/*1* Portable condition variable support for windows and pthreads.2* Everything is inline, this header can be included where needed.3*4* APIs generally return 0 on success and non-zero on error,5* and the caller needs to use its platform's error mechanism to6* discover the error (errno, or GetLastError())7*8* Note that some implementations cannot distinguish between a9* condition variable wait time-out and successful wait. Most often10* the difference is moot anyway since the wait condition must be11* re-checked.12* PyCOND_TIMEDWAIT, in addition to returning negative on error,13* thus returns 0 on regular success, 1 on timeout14* or 2 if it can't tell.15*16* There are at least two caveats with using these condition variables,17* due to the fact that they may be emulated with Semaphores on18* Windows:19* 1) While PyCOND_SIGNAL() will wake up at least one thread, we20* cannot currently guarantee that it will be one of the threads21* already waiting in a PyCOND_WAIT() call. It _could_ cause22* the wakeup of a subsequent thread to try a PyCOND_WAIT(),23* including the thread doing the PyCOND_SIGNAL() itself.24* The same applies to PyCOND_BROADCAST(), if N threads are waiting25* then at least N threads will be woken up, but not necessarily26* those already waiting.27* For this reason, don't make the scheduling assumption that a28* specific other thread will get the wakeup signal29* 2) The _mutex_ must be held when calling PyCOND_SIGNAL() and30* PyCOND_BROADCAST().31* While e.g. the posix standard strongly recommends that the mutex32* associated with the condition variable is held when a33* pthread_cond_signal() call is made, this is not a hard requirement,34* although scheduling will not be "reliable" if it isn't. Here35* the mutex is used for internal synchronization of the emulated36* Condition Variable.37*/3839#ifndef _CONDVAR_IMPL_H_40#define _CONDVAR_IMPL_H_4142#include "Python.h"43#include "pycore_condvar.h"4445#ifdef _POSIX_THREADS46/*47* POSIX support48*/4950/* These private functions are implemented in Python/thread_pthread.h */51int _PyThread_cond_init(PyCOND_T *cond);52void _PyThread_cond_after(long long us, struct timespec *abs);5354/* The following functions return 0 on success, nonzero on error */55#define PyMUTEX_INIT(mut) pthread_mutex_init((mut), NULL)56#define PyMUTEX_FINI(mut) pthread_mutex_destroy(mut)57#define PyMUTEX_LOCK(mut) pthread_mutex_lock(mut)58#define PyMUTEX_UNLOCK(mut) pthread_mutex_unlock(mut)5960#define PyCOND_INIT(cond) _PyThread_cond_init(cond)61#define PyCOND_FINI(cond) pthread_cond_destroy(cond)62#define PyCOND_SIGNAL(cond) pthread_cond_signal(cond)63#define PyCOND_BROADCAST(cond) pthread_cond_broadcast(cond)64#define PyCOND_WAIT(cond, mut) pthread_cond_wait((cond), (mut))6566/* return 0 for success, 1 on timeout, -1 on error */67Py_LOCAL_INLINE(int)68PyCOND_TIMEDWAIT(PyCOND_T *cond, PyMUTEX_T *mut, long long us)69{70struct timespec abs_timeout;71_PyThread_cond_after(us, &abs_timeout);72int ret = pthread_cond_timedwait(cond, mut, &abs_timeout);73if (ret == ETIMEDOUT) {74return 1;75}76if (ret) {77return -1;78}79return 0;80}8182#elif defined(NT_THREADS)83/*84* Windows (XP, 2003 server and later, as well as (hopefully) CE) support85*86* Emulated condition variables ones that work with XP and later, plus87* example native support on VISTA and onwards.88*/8990#if _PY_EMULATED_WIN_CV9192/* The mutex is a CriticalSection object and93The condition variables is emulated with the help of a semaphore.9495This implementation still has the problem that the threads woken96with a "signal" aren't necessarily those that are already97waiting. It corresponds to listing 2 in:98http://birrell.org/andrew/papers/ImplementingCVs.pdf99100Generic emulations of the pthread_cond_* API using101earlier Win32 functions can be found on the web.102The following read can be give background information to these issues,103but the implementations are all broken in some way.104http://www.cse.wustl.edu/~schmidt/win32-cv-1.html105*/106107Py_LOCAL_INLINE(int)108PyMUTEX_INIT(PyMUTEX_T *cs)109{110InitializeCriticalSection(cs);111return 0;112}113114Py_LOCAL_INLINE(int)115PyMUTEX_FINI(PyMUTEX_T *cs)116{117DeleteCriticalSection(cs);118return 0;119}120121Py_LOCAL_INLINE(int)122PyMUTEX_LOCK(PyMUTEX_T *cs)123{124EnterCriticalSection(cs);125return 0;126}127128Py_LOCAL_INLINE(int)129PyMUTEX_UNLOCK(PyMUTEX_T *cs)130{131LeaveCriticalSection(cs);132return 0;133}134135136Py_LOCAL_INLINE(int)137PyCOND_INIT(PyCOND_T *cv)138{139/* A semaphore with a "large" max value, The positive value140* is only needed to catch those "lost wakeup" events and141* race conditions when a timed wait elapses.142*/143cv->sem = CreateSemaphore(NULL, 0, 100000, NULL);144if (cv->sem==NULL)145return -1;146cv->waiting = 0;147return 0;148}149150Py_LOCAL_INLINE(int)151PyCOND_FINI(PyCOND_T *cv)152{153return CloseHandle(cv->sem) ? 0 : -1;154}155156/* this implementation can detect a timeout. Returns 1 on timeout,157* 0 otherwise (and -1 on error)158*/159Py_LOCAL_INLINE(int)160_PyCOND_WAIT_MS(PyCOND_T *cv, PyMUTEX_T *cs, DWORD ms)161{162DWORD wait;163cv->waiting++;164PyMUTEX_UNLOCK(cs);165/* "lost wakeup bug" would occur if the caller were interrupted here,166* but we are safe because we are using a semaphore which has an internal167* count.168*/169wait = WaitForSingleObjectEx(cv->sem, ms, FALSE);170PyMUTEX_LOCK(cs);171if (wait != WAIT_OBJECT_0)172--cv->waiting;173/* Here we have a benign race condition with PyCOND_SIGNAL.174* When failure occurs or timeout, it is possible that175* PyCOND_SIGNAL also decrements this value176* and signals releases the mutex. This is benign because it177* just means an extra spurious wakeup for a waiting thread.178* ('waiting' corresponds to the semaphore's "negative" count and179* we may end up with e.g. (waiting == -1 && sem.count == 1). When180* a new thread comes along, it will pass right through, having181* adjusted it to (waiting == 0 && sem.count == 0).182*/183184if (wait == WAIT_FAILED)185return -1;186/* return 0 on success, 1 on timeout */187return wait != WAIT_OBJECT_0;188}189190Py_LOCAL_INLINE(int)191PyCOND_WAIT(PyCOND_T *cv, PyMUTEX_T *cs)192{193int result = _PyCOND_WAIT_MS(cv, cs, INFINITE);194return result >= 0 ? 0 : result;195}196197Py_LOCAL_INLINE(int)198PyCOND_TIMEDWAIT(PyCOND_T *cv, PyMUTEX_T *cs, long long us)199{200return _PyCOND_WAIT_MS(cv, cs, (DWORD)(us/1000));201}202203Py_LOCAL_INLINE(int)204PyCOND_SIGNAL(PyCOND_T *cv)205{206/* this test allows PyCOND_SIGNAL to be a no-op unless required207* to wake someone up, thus preventing an unbounded increase of208* the semaphore's internal counter.209*/210if (cv->waiting > 0) {211/* notifying thread decreases the cv->waiting count so that212* a delay between notify and actual wakeup of the target thread213* doesn't cause a number of extra ReleaseSemaphore calls.214*/215cv->waiting--;216return ReleaseSemaphore(cv->sem, 1, NULL) ? 0 : -1;217}218return 0;219}220221Py_LOCAL_INLINE(int)222PyCOND_BROADCAST(PyCOND_T *cv)223{224int waiting = cv->waiting;225if (waiting > 0) {226cv->waiting = 0;227return ReleaseSemaphore(cv->sem, waiting, NULL) ? 0 : -1;228}229return 0;230}231232#else /* !_PY_EMULATED_WIN_CV */233234Py_LOCAL_INLINE(int)235PyMUTEX_INIT(PyMUTEX_T *cs)236{237InitializeSRWLock(cs);238return 0;239}240241Py_LOCAL_INLINE(int)242PyMUTEX_FINI(PyMUTEX_T *cs)243{244return 0;245}246247Py_LOCAL_INLINE(int)248PyMUTEX_LOCK(PyMUTEX_T *cs)249{250AcquireSRWLockExclusive(cs);251return 0;252}253254Py_LOCAL_INLINE(int)255PyMUTEX_UNLOCK(PyMUTEX_T *cs)256{257ReleaseSRWLockExclusive(cs);258return 0;259}260261262Py_LOCAL_INLINE(int)263PyCOND_INIT(PyCOND_T *cv)264{265InitializeConditionVariable(cv);266return 0;267}268Py_LOCAL_INLINE(int)269PyCOND_FINI(PyCOND_T *cv)270{271return 0;272}273274Py_LOCAL_INLINE(int)275PyCOND_WAIT(PyCOND_T *cv, PyMUTEX_T *cs)276{277return SleepConditionVariableSRW(cv, cs, INFINITE, 0) ? 0 : -1;278}279280/* This implementation makes no distinction about timeouts. Signal281* 2 to indicate that we don't know.282*/283Py_LOCAL_INLINE(int)284PyCOND_TIMEDWAIT(PyCOND_T *cv, PyMUTEX_T *cs, long long us)285{286return SleepConditionVariableSRW(cv, cs, (DWORD)(us/1000), 0) ? 2 : -1;287}288289Py_LOCAL_INLINE(int)290PyCOND_SIGNAL(PyCOND_T *cv)291{292WakeConditionVariable(cv);293return 0;294}295296Py_LOCAL_INLINE(int)297PyCOND_BROADCAST(PyCOND_T *cv)298{299WakeAllConditionVariable(cv);300return 0;301}302303304#endif /* _PY_EMULATED_WIN_CV */305306#endif /* _POSIX_THREADS, NT_THREADS */307308#endif /* _CONDVAR_IMPL_H_ */309310311