From edff26f320def7562009773ff8f258688d9860f6 Mon Sep 17 00:00:00 2001 From: rpj Date: Mon, 14 Mar 2005 01:30:17 +0000 Subject: '' --- pthread_once.c | 162 +++++++++++++++++++-------------------------------------- 1 file changed, 54 insertions(+), 108 deletions(-) (limited to 'pthread_once.c') diff --git a/pthread_once.c b/pthread_once.c index 1c0e01f..d27b49c 100644 --- a/pthread_once.c +++ b/pthread_once.c @@ -41,21 +41,20 @@ static void ptw32_once_init_routine_cleanup(void * arg) { + int oldCancelState; pthread_once_t * once_control = (pthread_once_t *) arg; - (void) PTW32_INTERLOCKED_EXCHANGE((LPLONG)&once_control->state, (LONG)PTW32_ONCE_CANCELLED); - (void) PTW32_INTERLOCKED_EXCHANGE((LPLONG)&once_control->started, (LONG)PTW32_FALSE); - - EnterCriticalSection(&ptw32_once_event_lock); - if (once_control->event) - { - /* - * There are waiters, wake some up - * We're deliberately not using PulseEvent. It's iffy, and deprecated. - */ - SetEvent(once_control->event); - } - LeaveCriticalSection(&ptw32_once_event_lock); + (void) PTW32_INTERLOCKED_EXCHANGE((LPLONG)&once_control->done, (LONG)PTW32_ONCE_CANCELLED); + (void) PTW32_INTERLOCKED_EXCHANGE((LPLONG)&once_control->started, -1L); + /* + * Wake everyone up. + * + * Holding the mutex during the broadcast prevents threads being left + * behind waiting. + */ + (void) pthread_mutex_lock(&ptw32_once_control.mtx); + (void) pthread_cond_broadcast(&ptw32_once_control.cond); + (void) pthread_mutex_unlock(&ptw32_once_control.mtx); } @@ -75,11 +74,6 @@ pthread_once (pthread_once_t * once_control, void (*init_routine) (void)) * access is controlled by the pthread_once_t control * key. * - * pthread_once() is not a cancelation point, but the init_routine - * can be. If it's cancelled then the effect on the once_control is - * as if pthread_once had never been entered. - * - * * PARAMETERS * once_control * pointer to an instance of pthread_once_t @@ -99,6 +93,7 @@ pthread_once (pthread_once_t * once_control, void (*init_routine) (void)) */ { int result; + int oldCancelState; if (once_control == NULL || init_routine == NULL) { @@ -112,113 +107,64 @@ pthread_once (pthread_once_t * once_control, void (*init_routine) (void)) result = 0; } - while (!(InterlockedExchangeAdd((LPLONG)&once_control->state, 0L) /* Atomic Read */ - & (LONG)PTW32_ONCE_DONE)) + /* + * Use a single global cond+mutex to manage access to all once_control objects. + * Unlike a global mutex on it's own, the global cond+mutex allows faster + * once_controls to overtake slower ones. Spurious wakeups may occur, but + * can be tolerated. + * + * To maintain a separate mutex for each once_control object requires either + * cleaning up the mutex (difficult to synchronise reliably), or leaving it + * around forever. Since we can't make assumptions about how an application might + * employ pthread_once objects, the later is considered to be unacceptable. + * + * Since this is being introduced as a bug fix, the global cond+mtx also avoids + * a change in the ABI, maintaining backwards compatibility. + */ + + while (!InterlockedExchangeAdd((LPLONG)&once_control->done, 0L) /* Full mem barrier read */ + & PTW32_ONCE_DONE) { - if (!PTW32_INTERLOCKED_EXCHANGE((LPLONG)&once_control->started, (LONG)PTW32_TRUE)) + if (PTW32_INTERLOCKED_EXCHANGE((LPLONG) &once_control->started, 0L) == -1) { - /* - * Clear residual state from a cancelled init_routine - * (and DONE still hasn't been set of course). - */ - if (PTW32_INTERLOCKED_EXCHANGE((LPLONG)&once_control->state, (LONG)PTW32_ONCE_CLEAR) - & PTW32_ONCE_CANCELLED) - { - /* - * The previous initter was cancelled. - * We now have a new initter (us) and we need to make the rest wait again. - */ - EnterCriticalSection(&ptw32_once_event_lock); - if (once_control->event) - { - ResetEvent(once_control->event); - } - LeaveCriticalSection(&ptw32_once_event_lock); - - /* - * Any threads entering the wait section and getting out again before - * the CANCELLED state can be cleared and the event is reset will, at worst, just go - * around again or, if they suspend and we (the initter) completes before they resume, - * they will see state == DONE and leave immediately. - */ - } + /* In case the previous initter was cancelled, reset cancelled state */ + (void) PTW32_INTERLOCKED_EXCHANGE((LPLONG)&once_control->done, (LONG)PTW32_ONCE_CLEAR); + +#ifdef _MSC_VER +#pragma inline_depth(0) +#endif - pthread_cleanup_push(ptw32_once_init_routine_cleanup, (void *) once_control); - (*init_routine)(); + pthread_cleanup_push(ptw32_once_init_routine_cleanup, (void*) once_control); + (*init_routine) (); pthread_cleanup_pop(0); - (void) PTW32_INTERLOCKED_EXCHANGE((LPLONG)&once_control->state, (LONG)PTW32_ONCE_DONE); +#ifdef _MSC_VER +#pragma inline_depth() +#endif /* - * we didn't create the event. - * it is only there if there is someone waiting + * Holding the mutex during the broadcast prevents threads being left + * behind waiting. */ - EnterCriticalSection(&ptw32_once_event_lock); - if (once_control->event) - { - SetEvent(once_control->event); - } - LeaveCriticalSection(&ptw32_once_event_lock); + (void) pthread_mutex_lock(&ptw32_once_control.mtx); + once_control->done = PTW32_TRUE; + (void) pthread_cond_broadcast(&ptw32_once_control.cond); + (void) pthread_mutex_unlock(&ptw32_once_control.mtx); } else { - /* - * wait for init. - * while waiting, create an event to wait on - */ - - EnterCriticalSection(&ptw32_once_event_lock); - once_control->eventUsers++; - - /* - * RE CANCELLATION: - * If we are the first thread after the initter thread, and the init_routine is cancelled - * while we're suspended at this point in the code:- - * - state will not get set to PTW32_ONCE_DONE; - * - cleanup will not see an event and cannot set it; - * - therefore, we will eventually resume, create an event and wait on it, maybe forever; - * Remedy: cleanup must set state == CANCELLED before checking for an event, so that - * we will see it and avoid waiting (as for state == DONE). We will go around again and - * we may become the initter. - * If we are still the only other thread when we get to the end of this block, we will - * have closed the event (good). If another thread beats us to be initter, then we will - * re-enter here (good). In case the old event is reused, the event is always reset by - * the new initter after clearing the CANCELLED state, causing any threads that are - * cycling around the loop to wait again. - */ - - if (!once_control->event) - { - once_control->event = CreateEvent(NULL, PTW32_TRUE, PTW32_FALSE, NULL); - } - LeaveCriticalSection(&ptw32_once_event_lock); - - /* - * check 'state' again in case the initting thread has finished or cancelled - * and left before seeing that there was an event to trigger. - * (Now that the event IS created, if init gets finished AFTER this, - * then the event handle is guaranteed to be seen and triggered). - */ - - if (!InterlockedExchangeAdd((LPLONG)&once_control->state, 0L)) /* Atomic Read */ + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldCancelState); + (void) pthread_mutex_lock(&ptw32_once_control.mtx); + while (!once_control->done) { /* Neither DONE nor CANCELLED */ - (void) WaitForSingleObject(once_control->event, INFINITE); + (void) pthread_cond_wait(&ptw32_once_control.cond, &ptw32_once_control.mtx); } - - /* last one out shut off the lights */ - EnterCriticalSection(&ptw32_once_event_lock); - if (0 == --once_control->eventUsers) - { - /* we were last */ - CloseHandle(once_control->event); - once_control->event = 0; - } - LeaveCriticalSection(&ptw32_once_event_lock); + (void) pthread_mutex_unlock(&ptw32_once_control.mtx); + pthread_setcancelstate(oldCancelState, NULL); } } - /* * Fall through Intentionally */ -- cgit v1.2.3