summaryrefslogtreecommitdiff
path: root/pthread_once.c
diff options
context:
space:
mode:
Diffstat (limited to 'pthread_once.c')
-rw-r--r--pthread_once.c162
1 files changed, 54 insertions, 108 deletions
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
*/