NetBSD-Bugs archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

lib/59126: pthread_once(3): missing memory ordering



>Number:         59126
>Category:       lib
>Synopsis:       pthread_once(3): missing memory ordering
>Confidential:   no
>Severity:       serious
>Priority:       medium
>Responsible:    lib-bug-people
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Tue Mar 04 00:20:00 +0000 2025
>Originator:     Taylor R Campbell
>Release:        current, 10, 9, ...
>Organization:
The NetBSD Oncelerbarrier
>Environment:
>Description:
The critical rule of pthread_once(O, I) is that any memory operations during I() in any thread happen-before all memory operations after pthread_once returns.

But the optimistic unlocked test in pthread_once does not guarantee this.  It needs membar_release/acquire at least.
>How-To-Repeat:
The following test program probably exhibits the issue on multicore machines with relaxed memory ordering (link with -pthread, run with the number of parallel threads to try, hit ^T for progress or ^C if you get tired of waiting):

#include <err.h>
#include <pthread.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

unsigned long long nloop;

static void
onsig(int signo)
{
	char buf[128];

	snprintf_ss(buf, sizeof(buf), "signal %d after %llu trials\n", signo,
	    nloop);
	(void)write(STDOUT_FILENO, buf, strlen(buf));
	if (signo == SIGINFO)
		return;
	(void)signal(signo, SIG_DFL);
	(void)raise(signo);
}

pthread_once_t once, once0 = PTHREAD_ONCE_INIT;
int done = 0;

static void
init(void)
{

	done = 1;
}

static void *
thread(void *cookie)
{
	pthread_barrier_t *bar = cookie;

	(void)pthread_barrier_wait(bar);
	pthread_once(&once, &init);
	if (!done)
		errx(1, "fail after %llu trials", nloop);
}

int
main(int argc, char **argv)
{
	enum { N = 256 };
	unsigned n = argc == 2 ? atoi(argv[1]) : 16;

	if (n < 1)
		errx(1, "not enough");
	if (n > N)
		errx(1, "too many");

	if (signal(SIGINT, &onsig) == SIG_ERR)
		err(1, "signal(SIGINT)");
	if (signal(SIGINFO, &onsig) == SIG_ERR)
		err(1, "signal(SIGINFO)");

	for (;; nloop++) {
		pthread_barrier_t bar;
		pthread_t t[N];
		unsigned i;
		int error;

		error = pthread_barrier_init(&bar, NULL, n);
		if (error)
			errc(1, error, "pthread_barrier_init");
		for (i = 0; i < n - 1; i++) {
			error = pthread_create(&t[i], NULL, &thread, &bar);
			if (error)
				errc(1, error, "pthread_create");
		}
		once = once0;
		done = 0;
		(void)pthread_barrier_wait(&bar);
		pthread_once(&once, &init);
		if (!done)
			errx(1, "fail");
		for (i = 0; i < n - 1; i++) {
			error = pthread_join(t[i], NULL);
			if (error)
				errc(1, error, "pthread_join");
		}
		error = pthread_barrier_destroy(&bar);
		if (error)
			errc(1, error, "pthread_barrier_destroy");
	}
}

>Fix:
membar_release/acquire



Home | Main Index | Thread Index | Old Index