| /* |
| * 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,2005 Pthreads-win32 contributors |
| * |
| * Contact Email: rpj@callisto.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; |
| * } |
| * ------------------------------------------------------------- |
| * |
| * Algorithm 9 / IMPL_SEM,UNBLOCK_STRATEGY == UNBLOCK_ALL |
| * |
| * presented below in pseudo-code; basically 8a... |
| * ...BUT W/O "spurious wakes" prevention: |
| * |
| * |
| * 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 ] |
| * |
| * sem_wait( semBlockLock ); |
| * ++nWaitersBlocked; |
| * sem_post( semBlockLock ); |
| * |
| * unlock( mtxExternal ); |
| * bTimedOut = sem_wait( semBlockQueue,timeout ); |
| * |
| * lock( mtxUnblockLock ); |
| * if ( 0 != (nSignalsWasLeft = nWaitersToUnblock) ) { |
| * --nWaitersToUnblock; |
| * } |
| * 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 ) { |
| * 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; |
| } ptw32_cond_wait_cleanup_args_t; |
| |
| static void PTW32_CDECL |
| 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 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)) |
| { |
| --(cv->nWaitersToUnblock); |
| } |
| else if (INT_MAX / 2 == ++(cv->nWaitersGone)) |
| { |
| /* Use the non-cancellable version of sem_wait() */ |
| if (ptw32_semwait (&(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 (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; |
| |
| /* Thread can be cancelled in sem_wait() but this is OK */ |
| 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 defined(_MSC_VER) && _MSC_VER < 1400 |
| #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; |
| } |
| } |
| |
| /* |
| * Always cleanup |
| */ |
| pthread_cleanup_pop (1); |
| #if defined(_MSC_VER) && _MSC_VER < 1400 |
| #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 */ |