# HG changeset patch
# User Taylor R Campbell <riastradh@NetBSD.org>
# Date 1741116631 0
#      Tue Mar 04 19:30:31 2025 +0000
# Branch trunk
# Node ID c3c49c7b641269af28e9463d41bba729af83a67f
# Parent  a866eb4bd00629dcb1025c7d2ae7d59255141de5
# EXP-Topic riastradh-pr59126-pthreadonceordering
WIP: libpthread/t_once: Test memory ordering.

XXX Doesn't work to trigger the problem -- even though a similar test
program I wrote, without any atf, does trigger it.

diff -r a866eb4bd006 -r c3c49c7b6412 tests/lib/libpthread/t_once.c
--- a/tests/lib/libpthread/t_once.c	Tue Mar 04 00:26:47 2025 +0000
+++ b/tests/lib/libpthread/t_once.c	Tue Mar 04 19:30:31 2025 +0000
@@ -31,7 +31,10 @@
  The NetBSD Foundation, inc. All rights reserved.");
 __RCSID("$NetBSD: t_once.c,v 1.2 2017/08/25 22:59:47 ginsbach Exp $");
 
+#include <sys/atomic.h>
+#include <sys/sysctl.h>
 #include <sys/time.h>
+
 #include <pthread.h>
 #include <signal.h>
 #include <stdio.h>
@@ -40,10 +43,12 @@
 #include <atf-c.h>
 
 #include "h_common.h"
+#include "h_macros.h"
 
 static pthread_once_t once = PTHREAD_ONCE_INIT;
 static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
 static int x;
+static unsigned long long ntrials;
 
 #define NTHREADS 25
 
@@ -187,11 +192,95 @@ ATF_TC_BODY(once3, tc)
 	printf("Test succeeded\n");
 }
 
+struct onceorder {
+	int done;
+	pthread_once_t once;
+} onceorder __aligned(256);
+
+static void
+onceorder_ofunc(void)
+{
+	/* fast and silent */
+	onceorder.done = 1;
+}
+
+static void *
+onceorder_thread(void *cookie)
+{
+	pthread_barrier_t *bar = cookie;
+
+	(void)pthread_barrier_wait(bar);
+	pthread_once(&onceorder.once, &onceorder_ofunc);
+	if (!onceorder.done)
+		atf_tc_fail("failed after %llu trials", ntrials);
+	return NULL;
+}
+
+ATF_TC(onceorder);
+ATF_TC_HEAD(onceorder, tc)
+{
+	atf_tc_set_md_var(tc, "descr",
+	    "Test memory ordering of pthread_once");
+}
+ATF_TC_BODY(onceorder, tc)
+{
+	static pthread_once_t once0 = PTHREAD_ONCE_INIT;
+	int ncpu;
+	size_t ncpulen;
+	pthread_barrier_t bar;
+//	pthread_t *t;
+	struct timespec start, now, elapsed, limit = {10,0};
+
+	ncpulen = sizeof(ncpu);
+	RL(sysctlbyname("hw.ncpu", &ncpu, &ncpulen, NULL, 0));
+	ATF_REQUIRE_EQ_MSG(ncpulen, sizeof(ncpu),
+	    "ncpulen=%zu sizeof(ncpu)=%zu", ncpulen, sizeof(ncpu));
+	if (ncpu <= 1)
+		atf_tc_skip("memory ordering relevant only on multiprocessor");
+
+	printf("once @ %p\n", &onceorder.once);
+	printf("done @ %p\n", &onceorder.done);
+	printf("x @ %p\n", &x);
+	fflush(stdout);
+
+//	REQUIRE_LIBC(t = calloc(ncpu - 1, sizeof(t[0])), NULL);
+
+	RZ(clock_gettime(CLOCK_MONOTONIC, &start));
+	do {
+		pthread_t t[255];
+		int i;
+
+		ATF_REQUIRE(ncpu <= 256);
+		RZ(pthread_barrier_init(&bar, NULL, ncpu));
+		for (i = 0; i < ncpu - 1; i++) {
+			RZ(pthread_create(&t[i], NULL, &onceorder_thread,
+				&bar));
+		}
+		onceorder.once = once0;
+		onceorder.done = 0;
+		(void)pthread_barrier_wait(&bar);
+		pthread_once(&onceorder.once, &onceorder_ofunc);
+		if (!onceorder.done)
+			atf_tc_fail("failed after %llu trials", ntrials);
+		for (i = 0; i < ncpu - 1; i++)
+			RZ(pthread_join(t[i], NULL));
+		RZ(pthread_barrier_destroy(&bar));
+
+		ntrials++;
+		RZ(clock_gettime(CLOCK_MONOTONIC, &now));
+		timespecsub(&now, &start, &elapsed);
+	} while (timespeccmp(&elapsed, &limit, <));
+
+	printf("no order violation detected after %llu trials in %d threads\n",
+	    ntrials, ncpu);
+}
+
 ATF_TP_ADD_TCS(tp)
 {
 	ATF_TP_ADD_TC(tp, once1);
 	ATF_TP_ADD_TC(tp, once2);
 	ATF_TP_ADD_TC(tp, once3);
+	ATF_TP_ADD_TC(tp, onceorder);
 
 	return atf_no_error();
 }