about summary refs log tree commit diff
diff options
context:
space:
mode:
authorRich Felker <dalias@aerifal.cx>2014-10-10 18:20:33 -0400
committerRich Felker <dalias@aerifal.cx>2015-03-30 01:34:13 -0400
commite3fa4300bf17d2b051bf21f451656af841404c2b (patch)
tree88fd25314e0c37dae2748caec01c47d91fc5ba11
parent02ccece69864c006bafd9c318402195c9d48aa4d (diff)
downloadmusl-e3fa4300bf17d2b051bf21f451656af841404c2b.tar.gz
musl-e3fa4300bf17d2b051bf21f451656af841404c2b.tar.xz
musl-e3fa4300bf17d2b051bf21f451656af841404c2b.zip
fix missing barrier in pthread_once/call_once shortcut path
these functions need to be fast when the init routine has already run,
since they may be called very often from code which depends on global
initialization having taken place. as such, a fast path bypassing
atomic cas on the once control object was used to avoid heavy memory
contention. however, on archs with weakly ordered memory, the fast
path failed to ensure that the caller actually observes the side
effects of the init routine.

preliminary performance testing showed that simply removing the fast
path was not practical; a performance drop of roughly 85x was observed
with 20 threads hammering the same once control on a 24-core machine.
so the new explicit barrier operation from atomic.h is used to retain
the fast path while ensuring memory visibility.

performance may be reduced on some archs where the barrier actually
makes a difference, but the previous behavior was unsafe and incorrect
on these archs. future improvements to the implementation of a_barrier
should reduce the impact.

(cherry picked from commit df37d3960abec482e17fad2274a99b790f6cc08b)

(edited not to depend on a_barrier, which is not available in 1.0.x)
-rw-r--r--src/thread/pthread_once.c9
1 files changed, 7 insertions, 2 deletions
diff --git a/src/thread/pthread_once.c b/src/thread/pthread_once.c
index e01f6d48..7b9ccf25 100644
--- a/src/thread/pthread_once.c
+++ b/src/thread/pthread_once.c
@@ -10,8 +10,13 @@ int pthread_once(pthread_once_t *control, void (*init)(void))
 {
 	static int waiters;
 
-	/* Return immediately if init finished before */
-	if (*control == 2) return 0;
+	/* Return immediately if init finished before, but ensure that
+	 * effects of the init routine are visible to the caller. */
+	if (*control == 2) {
+		/* Otherwise-useless cas just to get a barrier. */
+		a_cas(&(int){0},0,0);
+		return 0;
+	}
 
 	/* Try to enter initializing state. Three possibilities:
 	 *  0 - we're the first or the other cancelled; run init