Skip to content

Commit e1dd174

Browse files
committed
Issue python#8411: new condition variable emulation under Windows for the new GIL,
by Kristján. Unfortunately the 3.x Windows buildbots are in a wreck, so we'll have to watch them when they become fit again.
1 parent 817c9df commit e1dd174

1 file changed

Lines changed: 105 additions & 52 deletions

File tree

Python/ceval_gil.h

Lines changed: 105 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,6 @@ do { \
106106
#define COND_INIT(cond) \
107107
if (pthread_cond_init(&cond, NULL)) { \
108108
Py_FatalError("pthread_cond_init(" #cond ") failed"); };
109-
#define COND_RESET(cond)
110109
#define COND_SIGNAL(cond) \
111110
if (pthread_cond_signal(&cond)) { \
112111
Py_FatalError("pthread_cond_signal(" #cond ") failed"); };
@@ -141,64 +140,120 @@ do { \
141140

142141
#include <windows.h>
143142

144-
#define MUTEX_T HANDLE
145-
#define MUTEX_INIT(mut) \
146-
if (!(mut = CreateMutex(NULL, FALSE, NULL))) { \
147-
Py_FatalError("CreateMutex(" #mut ") failed"); };
143+
#define MUTEX_T CRITICAL_SECTION
144+
#define MUTEX_INIT(mut) do { \
145+
if (!(InitializeCriticalSectionAndSpinCount(&(mut), 4000))) \
146+
Py_FatalError("CreateMutex(" #mut ") failed"); \
147+
} while (0)
148+
#define MUTEX_FINI(mut) \
149+
DeleteCriticalSection(&(mut))
148150
#define MUTEX_LOCK(mut) \
149-
if (WaitForSingleObject(mut, INFINITE) != WAIT_OBJECT_0) { \
150-
Py_FatalError("WaitForSingleObject(" #mut ") failed"); };
151+
EnterCriticalSection(&(mut))
151152
#define MUTEX_UNLOCK(mut) \
152-
if (!ReleaseMutex(mut)) { \
153-
Py_FatalError("ReleaseMutex(" #mut ") failed"); };
154-
155-
/* We emulate condition variables with events. It is sufficient here.
156-
WaitForMultipleObjects() allows the event to be caught and the mutex
157-
to be taken atomically.
158-
As for SignalObjectAndWait(), its semantics are unfortunately a bit
159-
more foggy. Many sources on the Web define it as atomically releasing
160-
the first object while starting to wait on the second, but MSDN states
161-
it is *not* atomic...
162-
163-
In any case, the emulation here is tailored for our particular use case.
164-
For example, we don't care how many threads are woken up when a condition
165-
gets signalled. Generic emulations of the pthread_cond_* API using
153+
LeaveCriticalSection(&(mut))
154+
155+
/* We emulate condition variables with a semaphore.
156+
We use a Semaphore rather than an auto-reset event, because although
157+
an auto-resent event might appear to solve the lost-wakeup bug (race
158+
condition between releasing the outer lock and waiting) because it
159+
maintains state even though a wait hasn't happened, there is still
160+
a lost wakeup problem if more than one thread are interrupted in the
161+
critical place. A semaphore solves that.
162+
Because it is ok to signal a condition variable with no one
163+
waiting, we need to keep track of the number of
164+
waiting threads. Otherwise, the semaphore's state could rise
165+
without bound.
166+
167+
Generic emulations of the pthread_cond_* API using
166168
Win32 functions can be found on the Web.
167169
The following read can be edificating (or not):
168170
http://www.cse.wustl.edu/~schmidt/win32-cv-1.html
169171
*/
170-
#define COND_T HANDLE
172+
typedef struct COND_T
173+
{
174+
HANDLE sem; /* the semaphore */
175+
int n_waiting; /* how many are unreleased */
176+
} COND_T;
177+
178+
__inline static void _cond_init(COND_T *cond)
179+
{
180+
/* A semaphore with a large max value, The positive value
181+
* is only needed to catch those "lost wakeup" events and
182+
* race conditions when a timed wait elapses.
183+
*/
184+
if (!(cond->sem = CreateSemaphore(NULL, 0, 1000, NULL)))
185+
Py_FatalError("CreateSemaphore() failed");
186+
cond->n_waiting = 0;
187+
}
188+
189+
__inline static void _cond_fini(COND_T *cond)
190+
{
191+
BOOL ok = CloseHandle(cond->sem);
192+
if (!ok)
193+
Py_FatalError("CloseHandle() failed");
194+
}
195+
196+
__inline static void _cond_wait(COND_T *cond, MUTEX_T *mut)
197+
{
198+
++cond->n_waiting;
199+
MUTEX_UNLOCK(*mut);
200+
/* "lost wakeup bug" would occur if the caller were interrupted here,
201+
* but we are safe because we are using a semaphore wich has an internal
202+
* count.
203+
*/
204+
if (WaitForSingleObject(cond->sem, INFINITE) == WAIT_FAILED)
205+
Py_FatalError("WaitForSingleObject() failed");
206+
MUTEX_LOCK(*mut);
207+
}
208+
209+
__inline static int _cond_timed_wait(COND_T *cond, MUTEX_T *mut,
210+
int us)
211+
{
212+
DWORD r;
213+
++cond->n_waiting;
214+
MUTEX_UNLOCK(*mut);
215+
r = WaitForSingleObject(cond->sem, us / 1000);
216+
if (r == WAIT_FAILED)
217+
Py_FatalError("WaitForSingleObject() failed");
218+
MUTEX_LOCK(*mut);
219+
if (r == WAIT_TIMEOUT)
220+
--cond->n_waiting;
221+
/* Here we have a benign race condition with _cond_signal. If the
222+
* wait operation has timed out, but before we can acquire the
223+
* mutex again to decrement n_waiting, a thread holding the mutex
224+
* still sees a positive n_waiting value and may call
225+
* ReleaseSemaphore and decrement n_waiting.
226+
* This will cause n_waiting to be decremented twice.
227+
* This is benign, though, because ReleaseSemaphore will also have
228+
* been called, leaving the semaphore state positive. We may
229+
* thus end up with semaphore in state 1, and n_waiting == -1, and
230+
* the next time someone calls _cond_wait(), that thread will
231+
* pass right through, decrementing the semaphore state and
232+
* incrementing n_waiting, thus correcting the extra _cond_signal.
233+
*/
234+
return r == WAIT_TIMEOUT;
235+
}
236+
237+
__inline static void _cond_signal(COND_T *cond) {
238+
/* NOTE: This must be called with the mutex held */
239+
if (cond->n_waiting > 0) {
240+
if (!ReleaseSemaphore(cond->sem, 1, NULL))
241+
Py_FatalError("ReleaseSemaphore() failed");
242+
--cond->n_waiting;
243+
}
244+
}
245+
171246
#define COND_INIT(cond) \
172-
/* auto-reset, non-signalled */ \
173-
if (!(cond = CreateEvent(NULL, FALSE, FALSE, NULL))) { \
174-
Py_FatalError("CreateMutex(" #cond ") failed"); };
175-
#define COND_RESET(cond) \
176-
if (!ResetEvent(cond)) { \
177-
Py_FatalError("ResetEvent(" #cond ") failed"); };
247+
_cond_init(&(cond))
248+
#define COND_FINI(cond) \
249+
_cond_fini(&(cond))
178250
#define COND_SIGNAL(cond) \
179-
if (!SetEvent(cond)) { \
180-
Py_FatalError("SetEvent(" #cond ") failed"); };
251+
_cond_signal(&(cond))
181252
#define COND_WAIT(cond, mut) \
182-
{ \
183-
if (SignalObjectAndWait(mut, cond, INFINITE, FALSE) != WAIT_OBJECT_0) \
184-
Py_FatalError("SignalObjectAndWait(" #mut ", " #cond") failed"); \
185-
MUTEX_LOCK(mut); \
186-
}
187-
#define COND_TIMED_WAIT(cond, mut, microseconds, timeout_result) \
188-
{ \
189-
DWORD r; \
190-
HANDLE objects[2] = { cond, mut }; \
191-
MUTEX_UNLOCK(mut); \
192-
r = WaitForMultipleObjects(2, objects, TRUE, microseconds / 1000); \
193-
if (r == WAIT_TIMEOUT) { \
194-
MUTEX_LOCK(mut); \
195-
timeout_result = 1; \
196-
} \
197-
else if (r != WAIT_OBJECT_0) \
198-
Py_FatalError("WaitForSingleObject(" #cond ") failed"); \
199-
else \
200-
timeout_result = 0; \
201-
}
253+
_cond_wait(&(cond), &(mut))
254+
#define COND_TIMED_WAIT(cond, mut, us, timeout_result) do { \
255+
(timeout_result) = _cond_timed_wait(&(cond), &(mut), us); \
256+
} while (0)
202257

203258
#else
204259

@@ -282,7 +337,6 @@ static void drop_gil(PyThreadState *tstate)
282337
the GIL and drop it again, and reset the condition
283338
before we even had a chance to wait for it. */
284339
COND_WAIT(switch_cond, switch_mutex);
285-
COND_RESET(switch_cond);
286340
}
287341
MUTEX_UNLOCK(switch_mutex);
288342
}
@@ -301,7 +355,6 @@ static void take_gil(PyThreadState *tstate)
301355
if (!_Py_atomic_load_relaxed(&gil_locked))
302356
goto _ready;
303357

304-
COND_RESET(gil_cond);
305358
while (_Py_atomic_load_relaxed(&gil_locked)) {
306359
int timed_out = 0;
307360
unsigned long saved_switchnum;

0 commit comments

Comments
 (0)