diff --git a/Python/ceval_gil.h b/Python/ceval_gil.h --- a/Python/ceval_gil.h +++ b/Python/ceval_gil.h @@ -144,121 +144,42 @@ * Windows (2000 and later, as well as (hopefully) CE) support */ -#include +#include "thread_nt_cv.h" -#define MUTEX_T CRITICAL_SECTION +#define MUTEX_T _PY_WIN_CS #define MUTEX_INIT(mut) do { \ - if (!(InitializeCriticalSectionAndSpinCount(&(mut), 4000))) \ - Py_FatalError("CreateMutex(" #mut ") failed"); \ + if (_PyWinCS_Init(&(mut))) \ + Py_FatalError("_PyWinCS_Init() failed"); \ } while (0) #define MUTEX_FINI(mut) \ - DeleteCriticalSection(&(mut)) + _PyWinCS_Fini(&(mut)) #define MUTEX_LOCK(mut) \ - EnterCriticalSection(&(mut)) + _PyWinCS_Acquire(&(mut)) #define MUTEX_UNLOCK(mut) \ - LeaveCriticalSection(&(mut)) + _PyWinCS_Release(&(mut)) -/* We emulate condition variables with a semaphore. - We use a Semaphore rather than an auto-reset event, because although - an auto-resent event might appear to solve the lost-wakeup bug (race - condition between releasing the outer lock and waiting) because it - maintains state even though a wait hasn't happened, there is still - a lost wakeup problem if more than one thread are interrupted in the - critical place. A semaphore solves that. - Because it is ok to signal a condition variable with no one - waiting, we need to keep track of the number of - waiting threads. Otherwise, the semaphore's state could rise - without bound. - - Generic emulations of the pthread_cond_* API using - Win32 functions can be found on the Web. - The following read can be edificating (or not): - http://www.cse.wustl.edu/~schmidt/win32-cv-1.html -*/ -typedef struct COND_T -{ - HANDLE sem; /* the semaphore */ - int n_waiting; /* how many are unreleased */ -} COND_T; - -__inline static void _cond_init(COND_T *cond) -{ - /* A semaphore with a large max value, The positive value - * is only needed to catch those "lost wakeup" events and - * race conditions when a timed wait elapses. - */ - if (!(cond->sem = CreateSemaphore(NULL, 0, 1000, NULL))) - Py_FatalError("CreateSemaphore() failed"); - cond->n_waiting = 0; -} - -__inline static void _cond_fini(COND_T *cond) -{ - BOOL ok = CloseHandle(cond->sem); - if (!ok) - Py_FatalError("CloseHandle() failed"); -} - -__inline static void _cond_wait(COND_T *cond, MUTEX_T *mut) -{ - ++cond->n_waiting; - MUTEX_UNLOCK(*mut); - /* "lost wakeup bug" would occur if the caller were interrupted here, - * but we are safe because we are using a semaphore wich has an internal - * count. - */ - if (WaitForSingleObject(cond->sem, INFINITE) == WAIT_FAILED) - Py_FatalError("WaitForSingleObject() failed"); - MUTEX_LOCK(*mut); -} - -__inline static int _cond_timed_wait(COND_T *cond, MUTEX_T *mut, - int us) -{ - DWORD r; - ++cond->n_waiting; - MUTEX_UNLOCK(*mut); - r = WaitForSingleObject(cond->sem, us / 1000); - if (r == WAIT_FAILED) - Py_FatalError("WaitForSingleObject() failed"); - MUTEX_LOCK(*mut); - if (r == WAIT_TIMEOUT) - --cond->n_waiting; - /* Here we have a benign race condition with _cond_signal. If the - * wait operation has timed out, but before we can acquire the - * mutex again to decrement n_waiting, a thread holding the mutex - * still sees a positive n_waiting value and may call - * ReleaseSemaphore and decrement n_waiting. - * This will cause n_waiting to be decremented twice. - * This is benign, though, because ReleaseSemaphore will also have - * been called, leaving the semaphore state positive. We may - * thus end up with semaphore in state 1, and n_waiting == -1, and - * the next time someone calls _cond_wait(), that thread will - * pass right through, decrementing the semaphore state and - * incrementing n_waiting, thus correcting the extra _cond_signal. - */ - return r == WAIT_TIMEOUT; -} - -__inline static void _cond_signal(COND_T *cond) { - /* NOTE: This must be called with the mutex held */ - if (cond->n_waiting > 0) { - if (!ReleaseSemaphore(cond->sem, 1, NULL)) - Py_FatalError("ReleaseSemaphore() failed"); - --cond->n_waiting; - } -} - -#define COND_INIT(cond) \ - _cond_init(&(cond)) -#define COND_FINI(cond) \ - _cond_fini(&(cond)) -#define COND_SIGNAL(cond) \ - _cond_signal(&(cond)) -#define COND_WAIT(cond, mut) \ - _cond_wait(&(cond), &(mut)) +#define COND_T _PY_WIN_CV +#define COND_INIT(cond) do { \ + if (_PyWinCV_Init(&(cond))) \ + Py_FatalError("_PyWinCV_Init() failed"); \ +} while (0) +#define COND_FINI(cond) do { \ + if (_PyWinCV_Fini(&(cond))) \ + Py_FatalError("_PyWinCV_Fini() failed"); \ +} while (0) +#define COND_SIGNAL(cond) do { \ + if (_PyWinCV_Notify(&(cond))) \ + Py_FatalError("_PyWinCV_Notify() failed"); \ +} while (0) +#define COND_WAIT(cond, mut) do { \ + if (_PyWinCV_Wait(&(cond), &(mut), INFINITE) < 0) \ + Py_FatalError("_PyWinCV_Wait() failed"); \ +} while (0) #define COND_TIMED_WAIT(cond, mut, us, timeout_result) do { \ - (timeout_result) = _cond_timed_wait(&(cond), &(mut), us); \ + DWORD ms = us/1000; \ + (timeout_result) = _PyWinCV_Wait(&(cond), &(mut), ms); \ + if ((timeout_result) < 0) \ + Py_FatalError("_PyWinCV_Wait() failed"); \ } while (0) #else @@ -355,13 +276,13 @@ MUTEX_LOCK(switch_mutex); /* Not switched yet => wait */ if (_Py_atomic_load_relaxed(&gil_last_holder) == tstate) { - RESET_GIL_DROP_REQUEST(); + RESET_GIL_DROP_REQUEST(); /* NOTE: if COND_WAIT does not atomically start waiting when releasing the mutex, another thread can run through, take the GIL and drop it again, and reset the condition before we even had a chance to wait for it. */ COND_WAIT(switch_cond, switch_mutex); - } + } MUTEX_UNLOCK(switch_mutex); } #endif diff --git a/Python/thread_nt.h b/Python/thread_nt.h --- a/Python/thread_nt.h +++ b/Python/thread_nt.h @@ -9,6 +9,98 @@ #include #endif +/* code for condition variables on windows. Provide the native ones available + * on VISTA and onwards, along with emulated ones that work with XP and later + */ + +/* options */ +#ifndef _PY_USE_CV_LOCKS +#define _PY_USE_CV_LOCKS 1 /* use locks based on cond vars */ +#endif + +/* Now, define a non-recursive mutex using either condition variables + * and critical sections (fast) or using operating system mutexes + * (slow) + */ + +#if _PY_USE_CV_LOCKS + +#include "thread_nt_cv.h" + +typedef struct _NRMUTEX +{ + _PY_WIN_CS cs; + _PY_WIN_CV cv; + int locked; +} NRMUTEX; +typedef NRMUTEX *PNRMUTEX; + +PNRMUTEX +AllocNonRecursiveMutex() +{ + PNRMUTEX m = (PNRMUTEX)malloc(sizeof(NRMUTEX)); + if (!m) + return NULL; + if (_PyWinCV_Init(&m->cv)) { + free(m); + return NULL; + } + _PyWinCS_Init(&m->cs); + m->locked = 0; + return m; +} + +VOID +FreeNonRecursiveMutex(PNRMUTEX mutex) +{ + if (mutex) { + _PyWinCV_Fini(&mutex->cv); + _PyWinCS_Fini(&mutex->cs); + free(mutex); + } +} + +DWORD +EnterNonRecursiveMutex(PNRMUTEX mutex, DWORD milliseconds) +{ + DWORD result; + _PyWinCS_Acquire(&mutex->cs); + if (milliseconds == INFINITE) { + while (mutex->locked) + _PyWinCV_Wait(&mutex->cv, &mutex->cs, milliseconds); + } else if (milliseconds != 0) { + /* wait at least until the target */ + DWORD now, target = GetTickCount() + milliseconds; + while (mutex->locked) { + _PyWinCV_Wait(&mutex->cv, &mutex->cs, milliseconds); + now = GetTickCount(); + if (target <= now) + break; + milliseconds = target-now; + } + } + if (!mutex->locked) { + mutex->locked = 1; + result = WAIT_OBJECT_0; + } else + result = WAIT_TIMEOUT; + _PyWinCS_Release(&mutex->cs); + return result; +} + +BOOL +LeaveNonRecursiveMutex(PNRMUTEX mutex) +{ + _PyWinCS_Acquire(&mutex->cs); + mutex->locked = 0; + _PyWinCV_Notify(&mutex->cv); + _PyWinCS_Release(&mutex->cs); + return TRUE; +} + +#else + +/* NR-locks based on a kernel mutex */ #define PNRMUTEX HANDLE PNRMUTEX @@ -35,6 +127,7 @@ { return ReleaseSemaphore(mutex, 1, NULL); } +#endif long PyThread_get_thread_ident(void); diff --git a/Python/thread_nt_cv.h b/Python/thread_nt_cv.h new file mode 100644 --- /dev/null +++ b/Python/thread_nt_cv.h @@ -0,0 +1,194 @@ + +/* code for condition variables on windows. Provide the native ones available + * on VISTA and onwards, along with emulated ones that work with XP and later + * Everything is provided as inline code, for inclusion where needed + */ + +/* We emulate condition variables with a semaphore. + We use a Semaphore rather than an auto-reset event, because although + an auto-resent event might appear to solve the lost-wakeup bug (race + condition between releasing the outer lock and waiting) because it + maintains state even though a wait hasn't happened, there is still + a lost wakeup problem if more than one thread are interrupted in the + critical place. A semaphore solves that. + Because it is ok to signal a condition variable with no one + waiting, we need to keep track of the number of + waiting threads. Otherwise, the semaphore's state could rise + without bound. + + Generic emulations of the pthread_cond_* API using + Win32 functions can be found on the Web. + The following read can be edificating (or not): + http://www.cse.wustl.edu/~schmidt/win32-cv-1.html +*/ + + +#ifndef THREAD_NT_CV_H +#define THREAD_NT_CV_H + +/* include windows if it hasn't been done before */ +#define WIN32_LEAN_AND_MEAN +#include + +/* options */ +#ifndef _PY_EMULATED_WIN_CV +#define _PY_EMULATED_WIN_CV 0 /* use emulated condition variables */ +#endif + +#if !defined NTDDI_VISTA || NTDDI_VERSION < NTDDI_VISTA +#undef _PY_EMULATED_WIN_CV +#define _PY_EMULATED_WIN_CV 1 +#endif + +/* the "critical section" object to use with the condition variable. + * use the fast SRWLock if available since it outperforms + * the classic CriticalSection objects + */ +#if _PY_EMULATED_WIN_CV +typedef CRITICAL_SECTION _PY_WIN_CS; +static __inline int +_PyWinCS_Init(_PY_WIN_CS *cs) +{ + InitializeCriticalSection(cs); + return 0; +} +static __inline void +_PyWinCS_Fini(_PY_WIN_CS *cs) +{ + DeleteCriticalSection(cs); +} +static __inline void +_PyWinCS_Acquire(_PY_WIN_CS *cs) +{ + EnterCriticalSection(cs); +} +static __inline void +_PyWinCS_Release(_PY_WIN_CS *cs) +{ + LeaveCriticalSection(cs); +} + +#else + +typedef SRWLOCK _PY_WIN_CS; +static __inline int +_PyWinCS_Init(_PY_WIN_CS *cs) +{ + InitializeSRWLock(cs); + return 0; +} +static __inline void +_PyWinCS_Fini(_PY_WIN_CS *cs) +{} +static __inline void +_PyWinCS_Acquire(_PY_WIN_CS *cs) +{ + AcquireSRWLockExclusive(cs); +} +static __inline void +_PyWinCS_Release(_PY_WIN_CS *cs) +{ + ReleaseSRWLockExclusive(cs); +} + +#endif + +/* The ConditionVariable object. From XP onwards it is easily emulated with + * a Semaphore + */ + +#if _PY_EMULATED_WIN_CV +typedef struct __PY_WIN_CV +{ + HANDLE sem; + int waiting; +} _PY_WIN_CV; + +static __inline int +_PyWinCV_Init(_PY_WIN_CV *cv) +{ + /* A semaphore with a large max value, The positive value + * is only needed to catch those "lost wakeup" events and + * race conditions when a timed wait elapses. + */ + cv->sem = CreateSemaphore(NULL, 0, 100000, NULL); + if (cv->sem==NULL) + return -1; + cv->waiting = 0; + return 0; +} +static __inline int +_PyWinCV_Fini(_PY_WIN_CV *cv) +{ + return CloseHandle(cv->sem) ? 0 : -1; +} +static __inline int +_PyWinCV_Wait(_PY_WIN_CV *cv, _PY_WIN_CS *cs, DWORD ms) +{ + DWORD wait; + cv->waiting++; + _PyWinCS_Release(cs); + /* "lost wakeup bug" would occur if the caller were interrupted here, + * but we are safe because we are using a semaphore wich has an internal + * count. + */ + wait = WaitForSingleObject(cv->sem, ms); + _PyWinCS_Acquire(cs); + if (wait != WAIT_OBJECT_0) + --cv->waiting; + /* Here we have a benign race condition with _PyWinCV_Notify. + * When failure occurs or timeout, it is possible that + * _PyWinCV_Notify also decrements this value + * and signals releases the mutex. This is benign because it + * just means an extra spurious wakeup for a waiting thread. + */ + + if (wait == WAIT_FAILED) + return -1; + return wait == WAIT_OBJECT_0; +} + +static __inline int +_PyWinCV_Notify(_PY_WIN_CV *cv) +{ + if (cv->waiting) { + /* notifying thread decreases the cv->waiting count so that + * a delay between notify and wakeup doesn"t cause a number + * of extra ReleaseSemaphore calls + */ + cv->waiting--; + return ReleaseSemaphore(cv->sem, 1, NULL) ? 0 : -1; + } + return 0; +} + +#else + +typedef CONDITION_VARIABLE _PY_WIN_CV; + +static __inline int +_PyWinCV_Init(_PY_WIN_CV *cv) +{ + InitializeConditionVariable(cv); + return 0; +} +static __inline int +_PyWinCV_Fini(_PY_WIN_CV *cv) +{ + return 0; +} +static __inline int +_PyWinCV_Wait(_PY_WIN_CV *cv, _PY_WIN_CS *cs, DWORD ms) +{ + return SleepConditionVariableSRW(cv, cs, ms, 0) ? 0 : -1; +} +static __inline int +_PyWinCV_Notify(_PY_WIN_CV *cv) +{ + WakeConditionVariable(cv); + return 0; +} + +#endif + +#endif /* THREAD_NT_CV_H */ \ No newline at end of file