about summary refs log tree commit diff
path: root/src/thread/pthread_once.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/thread/pthread_once.c')
-rw-r--r--src/thread/pthread_once.c22
1 files changed, 13 insertions, 9 deletions
diff --git a/src/thread/pthread_once.c b/src/thread/pthread_once.c
index b7388b93..7c47385c 100644
--- a/src/thread/pthread_once.c
+++ b/src/thread/pthread_once.c
@@ -2,14 +2,14 @@
 
 static void undo(void *control)
 {
-	a_store(control, 0);
-	__wake(control, 1, 1);
+	/* Wake all waiters, since the waiter status is lost when
+	 * resetting control to the initial state. */
+	if (a_swap(control, 0) == 3)
+		__wake(control, -1, 1);
 }
 
 int __pthread_once(pthread_once_t *control, void (*init)(void))
 {
-	static int waiters;
-
 	/* Return immediately if init finished before, but ensure that
 	 * effects of the init routine are visible to the caller. */
 	if (*control == 2) {
@@ -17,10 +17,11 @@ int __pthread_once(pthread_once_t *control, void (*init)(void))
 		return 0;
 	}
 
-	/* Try to enter initializing state. Three possibilities:
+	/* Try to enter initializing state. Four possibilities:
 	 *  0 - we're the first or the other cancelled; run init
 	 *  1 - another thread is running init; wait
-	 *  2 - another thread finished running init; just return */
+	 *  2 - another thread finished running init; just return
+	 *  3 - another thread is running init, waiters present; wait */
 
 	for (;;) switch (a_cas(control, 0, 1)) {
 	case 0:
@@ -28,11 +29,14 @@ int __pthread_once(pthread_once_t *control, void (*init)(void))
 		init();
 		pthread_cleanup_pop(0);
 
-		a_store(control, 2);
-		if (waiters) __wake(control, -1, 1);
+		if (a_swap(control, 2) == 3)
+			__wake(control, -1, 1);
 		return 0;
 	case 1:
-		__wait(control, &waiters, 1, 1);
+		/* If this fails, so will __wait. */
+		a_cas(control, 1, 3);
+	case 3:
+		__wait(control, 0, 3, 1);
 		continue;
 	case 2:
 		return 0;