@@ -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