diff options
Diffstat (limited to 'pthread_cond_wait.c')
-rw-r--r-- | pthread_cond_wait.c | 527 |
1 files changed, 527 insertions, 0 deletions
diff --git a/pthread_cond_wait.c b/pthread_cond_wait.c new file mode 100644 index 0000000..d1f5155 --- /dev/null +++ b/pthread_cond_wait.c @@ -0,0 +1,527 @@ +/* + * pthread_cond_wait.c + * + * Description: + * This translation unit implements condition variables and their primitives. + * + * + * -------------------------------------------------------------------------- + * + * Pthreads-win32 - POSIX Threads Library for Win32 + * Copyright(C) 1998 John E. Bossom + * Copyright(C) 1999,2002 Pthreads-win32 contributors + * + * Contact Email: rpj@ise.canberra.edu.au + * + * The current list of contributors is contained + * in the file CONTRIBUTORS included with the source + * code distribution. The list can also be seen at the + * following World Wide Web location: + * http://sources.redhat.com/pthreads-win32/contributors.html + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library in the file COPYING.LIB; + * if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + * ------------------------------------------------------------- + * Algorithm: + * The algorithm used in this implementation is that developed by + * Alexander Terekhov in colaboration with Louis Thomas. The bulk + * of the discussion is recorded in the file README.CV, which contains + * several generations of both colaborators original algorithms. The final + * algorithm used here is the one referred to as + * + * Algorithm 8a / IMPL_SEM,UNBLOCK_STRATEGY == UNBLOCK_ALL + * + * presented below in pseudo-code as it appeared: + * + * + * given: + * semBlockLock - bin.semaphore + * semBlockQueue - semaphore + * mtxExternal - mutex or CS + * mtxUnblockLock - mutex or CS + * nWaitersGone - int + * nWaitersBlocked - int + * nWaitersToUnblock - int + * + * wait( timeout ) { + * + * [auto: register int result ] // error checking omitted + * [auto: register int nSignalsWasLeft ] + * [auto: register int nWaitersWasGone ] + * + * sem_wait( semBlockLock ); + * nWaitersBlocked++; + * sem_post( semBlockLock ); + * + * unlock( mtxExternal ); + * bTimedOut = sem_wait( semBlockQueue,timeout ); + * + * lock( mtxUnblockLock ); + * if ( 0 != (nSignalsWasLeft = nWaitersToUnblock) ) { + * if ( bTimeout ) { // timeout (or canceled) + * if ( 0 != nWaitersBlocked ) { + * nWaitersBlocked--; + * } + * else { + * nWaitersGone++; // count spurious wakeups. + * } + * } + * if ( 0 == --nWaitersToUnblock ) { + * if ( 0 != nWaitersBlocked ) { + * sem_post( semBlockLock ); // open the gate. + * nSignalsWasLeft = 0; // do not open the gate + * // below again. + * } + * else if ( 0 != (nWaitersWasGone = nWaitersGone) ) { + * nWaitersGone = 0; + * } + * } + * } + * else if ( INT_MAX/2 == ++nWaitersGone ) { // timeout/canceled or + * // spurious semaphore :-) + * sem_wait( semBlockLock ); + * nWaitersBlocked -= nWaitersGone; // something is going on here + * // - test of timeouts? :-) + * sem_post( semBlockLock ); + * nWaitersGone = 0; + * } + * unlock( mtxUnblockLock ); + * + * if ( 1 == nSignalsWasLeft ) { + * if ( 0 != nWaitersWasGone ) { + * // sem_adjust( semBlockQueue,-nWaitersWasGone ); + * while ( nWaitersWasGone-- ) { + * sem_wait( semBlockQueue ); // better now than spurious later + * } + * } sem_post( semBlockLock ); // open the gate + * } + * + * lock( mtxExternal ); + * + * return ( bTimedOut ) ? ETIMEOUT : 0; + * } + * + * signal(bAll) { + * + * [auto: register int result ] + * [auto: register int nSignalsToIssue] + * + * lock( mtxUnblockLock ); + * + * if ( 0 != nWaitersToUnblock ) { // the gate is closed!!! + * if ( 0 == nWaitersBlocked ) { // NO-OP + * return unlock( mtxUnblockLock ); + * } + * if (bAll) { + * nWaitersToUnblock += nSignalsToIssue=nWaitersBlocked; + * nWaitersBlocked = 0; + * } + * else { + * nSignalsToIssue = 1; + * nWaitersToUnblock++; + * nWaitersBlocked--; + * } + * } + * else if ( nWaitersBlocked > nWaitersGone ) { // HARMLESS RACE CONDITION! + * sem_wait( semBlockLock ); // close the gate + * if ( 0 != nWaitersGone ) { + * nWaitersBlocked -= nWaitersGone; + * nWaitersGone = 0; + * } + * if (bAll) { + * nSignalsToIssue = nWaitersToUnblock = nWaitersBlocked; + * nWaitersBlocked = 0; + * } + * else { + * nSignalsToIssue = nWaitersToUnblock = 1; + * nWaitersBlocked--; + * } + * } + * else { // NO-OP + * return unlock( mtxUnblockLock ); + * } + * + * unlock( mtxUnblockLock ); + * sem_post( semBlockQueue,nSignalsToIssue ); + * return result; + * } + * ------------------------------------------------------------- + * + */ + +#include "pthread.h" +#include "implement.h" + + +/* + * Arguments for cond_wait_cleanup, since we can only pass a + * single void * to it. + */ +typedef struct { + pthread_mutex_t * mutexPtr; + pthread_cond_t cv; + int * resultPtr; + int signaled; +} ptw32_cond_wait_cleanup_args_t; + +static void +ptw32_cond_wait_cleanup(void * args) +{ + ptw32_cond_wait_cleanup_args_t * cleanup_args = (ptw32_cond_wait_cleanup_args_t *) args; + pthread_cond_t cv = cleanup_args->cv; + int * resultPtr = cleanup_args->resultPtr; + int nSignalsWasLeft; + int nWaitersWasGone = 0; /* Initialised to quell warnings. */ + int result; + + /* + * Whether we got here as a result of signal/broadcast or because of + * timeout on wait or thread cancellation we indicate that we are no + * longer waiting. The waiter is responsible for adjusting waiters + * (to)unblock(ed) counts (protected by unblock lock). + */ + if ((result = pthread_mutex_lock(&(cv->mtxUnblockLock))) != 0) + { + *resultPtr = result; + return; + } + + if ( 0 != (nSignalsWasLeft = cv->nWaitersToUnblock) ) + { + if ( !cleanup_args->signaled ) + { + if ( 0 != cv->nWaitersBlocked ) + { + (cv->nWaitersBlocked)--; + } + else + { + (cv->nWaitersGone)++; + } + } + if ( 0 == --(cv->nWaitersToUnblock) ) + { + if ( 0 != cv->nWaitersBlocked ) + { + if (sem_post( &(cv->semBlockLock) ) != 0) + { + *resultPtr = errno; + /* + * This is a fatal error for this CV, + * so we deliberately don't unlock + * cv->mtxUnblockLock before returning. + */ + return; + } + nSignalsWasLeft = 0; + } + else if ( 0 != (nWaitersWasGone = cv->nWaitersGone) ) + { + cv->nWaitersGone = 0; + } + } + } + else if ( INT_MAX/2 == ++(cv->nWaitersGone) ) + { + if (sem_wait( &(cv->semBlockLock) ) != 0) + { + *resultPtr = errno; + /* + * This is a fatal error for this CV, + * so we deliberately don't unlock + * cv->mtxUnblockLock before returning. + */ + return; + } + cv->nWaitersBlocked -= cv->nWaitersGone; + if (sem_post( &(cv->semBlockLock) ) != 0) + { + *resultPtr = errno; + /* + * This is a fatal error for this CV, + * so we deliberately don't unlock + * cv->mtxUnblockLock before returning. + */ + return; + } + cv->nWaitersGone = 0; + } + + if ((result = pthread_mutex_unlock(&(cv->mtxUnblockLock))) != 0) + { + *resultPtr = result; + return; + } + + if ( 1 == nSignalsWasLeft ) + { + if ( 0 != nWaitersWasGone ) + { + // sem_adjust( &(cv->semBlockQueue), -nWaitersWasGone ); + while ( nWaitersWasGone-- ) + { + if (sem_wait( &(cv->semBlockQueue)) != 0 ) + { + *resultPtr = errno; + return; + } + } + } + if (sem_post(&(cv->semBlockLock)) != 0) + { + *resultPtr = errno; + return; + } + } + + /* + * XSH: Upon successful return, the mutex has been locked and is owned + * by the calling thread + */ + if ((result = pthread_mutex_lock(cleanup_args->mutexPtr)) != 0) + { + *resultPtr = result; + } + +} /* ptw32_cond_wait_cleanup */ + +static INLINE int +ptw32_cond_timedwait (pthread_cond_t * cond, + pthread_mutex_t * mutex, + const struct timespec *abstime) +{ + int result = 0; + pthread_cond_t cv; + ptw32_cond_wait_cleanup_args_t cleanup_args; + + if (cond == NULL || *cond == NULL) + { + return EINVAL; + } + + /* + * We do a quick check to see if we need to do more work + * to initialise a static condition variable. We check + * again inside the guarded section of ptw32_cond_check_need_init() + * to avoid race conditions. + */ + if (*cond == PTHREAD_COND_INITIALIZER) + { + result = ptw32_cond_check_need_init(cond); + } + + if (result != 0 && result != EBUSY) + { + return result; + } + + cv = *cond; + + if (sem_wait(&(cv->semBlockLock)) != 0) + { + return errno; + } + + cv->nWaitersBlocked++; + + if (sem_post(&(cv->semBlockLock)) != 0) + { + return errno; + } + + /* + * Setup this waiter cleanup handler + */ + cleanup_args.mutexPtr = mutex; + cleanup_args.cv = cv; + cleanup_args.resultPtr = &result; + /* + * If we're canceled, or the cancelable wait fails for any reason, + * including a timeout, then tell the cleanup routine that we + * have not been signaled. + */ + cleanup_args.signaled = 0; + +#ifdef _MSC_VER +#pragma inline_depth(0) +#endif + pthread_cleanup_push(ptw32_cond_wait_cleanup, (void *) &cleanup_args); + + /* + * Now we can release 'mutex' and... + */ + if ((result = pthread_mutex_unlock(mutex)) == 0) + { + + /* + * ...wait to be awakened by + * pthread_cond_signal, or + * pthread_cond_broadcast, or + * timeout, or + * thread cancellation + * + * Note: + * + * sem_timedwait is a cancellation point, + * hence providing the mechanism for making + * pthread_cond_wait a cancellation point. + * We use the cleanup mechanism to ensure we + * re-lock the mutex and adjust (to)unblock(ed) waiters + * counts if we are cancelled, timed out or signalled. + */ + if (sem_timedwait(&(cv->semBlockQueue), abstime) != 0) + { + result = errno; + } + } + + /* + * Not executed if we're canceled. Signaled is false if we timed out. + */ + cleanup_args.signaled = (result == 0); + + /* + * Always cleanup + */ + pthread_cleanup_pop(1); +#ifdef _MSC_VER +#pragma inline_depth() +#endif + + /* + * "result" can be modified by the cleanup handler. + */ + return result; + +} /* ptw32_cond_timedwait */ + + +int +pthread_cond_wait (pthread_cond_t * cond, + pthread_mutex_t * mutex) + /* + * ------------------------------------------------------ + * DOCPUBLIC + * This function waits on a condition variable until + * awakened by a signal or broadcast. + * + * Caller MUST be holding the mutex lock; the + * lock is released and the caller is blocked waiting + * on 'cond'. When 'cond' is signaled, the mutex + * is re-acquired before returning to the caller. + * + * PARAMETERS + * cond + * pointer to an instance of pthread_cond_t + * + * mutex + * pointer to an instance of pthread_mutex_t + * + * + * DESCRIPTION + * This function waits on a condition variable until + * awakened by a signal or broadcast. + * + * NOTES: + * + * 1) The function must be called with 'mutex' LOCKED + * by the calling thread, or undefined behaviour + * will result. + * + * 2) This routine atomically releases 'mutex' and causes + * the calling thread to block on the condition variable. + * The blocked thread may be awakened by + * pthread_cond_signal or + * pthread_cond_broadcast. + * + * Upon successful completion, the 'mutex' has been locked and + * is owned by the calling thread. + * + * + * RESULTS + * 0 caught condition; mutex released, + * EINVAL 'cond' or 'mutex' is invalid, + * EINVAL different mutexes for concurrent waits, + * EINVAL mutex is not held by the calling thread, + * + * ------------------------------------------------------ + */ +{ + /* + * The NULL abstime arg means INFINITE waiting. + */ + return (ptw32_cond_timedwait(cond, mutex, NULL)); + +} /* pthread_cond_wait */ + + +int +pthread_cond_timedwait (pthread_cond_t * cond, + pthread_mutex_t * mutex, + const struct timespec *abstime) + /* + * ------------------------------------------------------ + * DOCPUBLIC + * This function waits on a condition variable either until + * awakened by a signal or broadcast; or until the time + * specified by abstime passes. + * + * PARAMETERS + * cond + * pointer to an instance of pthread_cond_t + * + * mutex + * pointer to an instance of pthread_mutex_t + * + * abstime + * pointer to an instance of (const struct timespec) + * + * + * DESCRIPTION + * This function waits on a condition variable either until + * awakened by a signal or broadcast; or until the time + * specified by abstime passes. + * + * NOTES: + * 1) The function must be called with 'mutex' LOCKED + * by the calling thread, or undefined behaviour + * will result. + * + * 2) This routine atomically releases 'mutex' and causes + * the calling thread to block on the condition variable. + * The blocked thread may be awakened by + * pthread_cond_signal or + * pthread_cond_broadcast. + * + * + * RESULTS + * 0 caught condition; mutex released, + * EINVAL 'cond', 'mutex', or abstime is invalid, + * EINVAL different mutexes for concurrent waits, + * EINVAL mutex is not held by the calling thread, + * ETIMEDOUT abstime ellapsed before cond was signaled. + * + * ------------------------------------------------------ + */ +{ + if (abstime == NULL) + { + return EINVAL; + } + + return (ptw32_cond_timedwait(cond, mutex, abstime)); + +} /* pthread_cond_timedwait */ |