about summary refs log tree commit diff
diff options
context:
space:
mode:
authorRich Felker <dalias@aerifal.cx>2014-04-15 20:42:39 -0400
committerRich Felker <dalias@aerifal.cx>2014-04-15 20:42:39 -0400
commit0d0c2f40344640a2a6942dda156509593f51db5d (patch)
treebaf15b9f4105f040e47e10b9bdaa56ad8e7c7fc5
parentfcea534e579077e10456f6ed06c033dfaa013a24 (diff)
downloadmusl-0d0c2f40344640a2a6942dda156509593f51db5d.tar.gz
musl-0d0c2f40344640a2a6942dda156509593f51db5d.tar.xz
musl-0d0c2f40344640a2a6942dda156509593f51db5d.zip
fix deadlock race in pthread_once
at the end of successful pthread_once, there was a race window during
which another thread calling pthread_once would momentarily change the
state back from 2 (finished) to 1 (in-progress). in this case, the
status was immediately changed back, but with no wake call, meaning
that waiters which arrived during this short window could block
forever. there are two possible fixes. one would be adding the wake to
the code path where it was missing. but it's better just to avoid
reverting the status at all, by using compare-and-swap instead of
swap.
-rw-r--r--src/thread/pthread_once.c3
1 files changed, 1 insertions, 2 deletions
diff --git a/src/thread/pthread_once.c b/src/thread/pthread_once.c
index 41872f16..e01f6d48 100644
--- a/src/thread/pthread_once.c
+++ b/src/thread/pthread_once.c
@@ -18,7 +18,7 @@ int pthread_once(pthread_once_t *control, void (*init)(void))
 	 *  1 - another thread is running init; wait
 	 *  2 - another thread finished running init; just return */
 
-	for (;;) switch (a_swap(control, 1)) {
+	for (;;) switch (a_cas(control, 0, 1)) {
 	case 0:
 		pthread_cleanup_push(undo, control);
 		init();
@@ -31,7 +31,6 @@ int pthread_once(pthread_once_t *control, void (*init)(void))
 		__wait(control, &waiters, 1, 0);
 		continue;
 	case 2:
-		a_store(control, 2);
 		return 0;
 	}
 }