From 9c6e968e37e1c67eaaddd7b033fbd068ee1d021a Mon Sep 17 00:00:00 2001
From: Harrison Booth <harrisontbooth@gmail.com>
Date: Mon, 12 Oct 2026 20:01:15 -0500
Subject: [PATCH v1] Fix Windows ECPG pthread_once publication ordering

Use acquire reads in both the public macro fast path and the once helper,
and publish the completed initialization with a release store. A volatile
read in the helper cannot protect callers that bypass it through the
ordinary Boolean read in the macro.

Keep pthread_once_t as bool and retain the existing function signature.
Use byte acquire/release operations with MSVC and GNU atomic operations,
and constrain compiler reordering on MSVC x86/x64 even with volatile:iso.

A source-extracted ARM64 stress probe observes initialized flags before
callback data becomes visible on the original code. The revised protocol
passes native ARM64, emulated x64, and MinGW controls. Full native ARM64
and emulated x64 ECPG regression suites pass with the validation setup.
---
 src/interfaces/ecpg/ecpglib/misc.c            | 13 ++++++++---
 .../ecpg/include/ecpg-pthread-win32.h         | 23 ++++++++++++++++++-
 2 files changed, 32 insertions(+), 4 deletions(-)

diff --git a/src/interfaces/ecpg/ecpglib/misc.c b/src/interfaces/ecpg/ecpglib/misc.c
index 354a2e731fc..34d42b37ae1 100644
--- a/src/interfaces/ecpg/ecpglib/misc.c
+++ b/src/interfaces/ecpg/ecpglib/misc.c
@@ -465,13 +465,20 @@ static pthread_mutex_t win32_pthread_once_lock = PTHREAD_MUTEX_INITIALIZER;
 void
 win32_pthread_once(volatile pthread_once_t *once, void (*fn) (void))
 {
-	if (!*once)
+	if (!win32_pthread_once_done(once))
 	{
 		pthread_mutex_lock(&win32_pthread_once_lock);
-		if (!*once)
+		if (!win32_pthread_once_done(once))
 		{
 			fn();
-			*once = true;
+#ifdef _MSC_VER
+#if defined(_M_IX86) || defined(_M_X64)
+			_ReadWriteBarrier();
+#endif
+			WriteRelease8((volatile CHAR *) once, 1);
+#else
+			__atomic_store_n(once, true, __ATOMIC_RELEASE);
+#endif
 		}
 		pthread_mutex_unlock(&win32_pthread_once_lock);
 	}
diff --git a/src/interfaces/ecpg/include/ecpg-pthread-win32.h b/src/interfaces/ecpg/include/ecpg-pthread-win32.h
index 7b6ba46b349..d933f07420d 100644
--- a/src/interfaces/ecpg/include/ecpg-pthread-win32.h
+++ b/src/interfaces/ecpg/include/ecpg-pthread-win32.h
@@ -10,6 +10,10 @@
 #include <pthread.h>
 #else
 
+#ifdef _MSC_VER
+#include <intrin.h>
+#endif
+
 typedef struct pthread_mutex_t
 {
 	/* initstate = 0: not initialized; 1: init done; 2: init in progress */
@@ -29,6 +33,23 @@ int			pthread_mutex_unlock(pthread_mutex_t *);
 
 void		win32_pthread_once(volatile pthread_once_t *once, void (*fn) (void));
 
+/* Acquire the initialization function's effects before using its result. */
+static inline bool
+win32_pthread_once_done(volatile pthread_once_t *once)
+{
+#ifdef _MSC_VER
+	bool		done = ReadAcquire8((const volatile CHAR *) once) != 0;
+
+#if defined(_M_IX86) || defined(_M_X64)
+	/* Also constrain compiler reordering with /volatile:iso on x86/x64. */
+	_ReadWriteBarrier();
+#endif
+	return done;
+#else
+	return __atomic_load_n(once, __ATOMIC_ACQUIRE);
+#endif
+}
+
 #define pthread_getspecific(key) \
 	TlsGetValue((key))
 
@@ -41,7 +62,7 @@ void		win32_pthread_once(volatile pthread_once_t *once, void (*fn) (void));
 
 #define pthread_once(once, fn) \
 	do { \
-		if (!*(once)) \
+		if (!win32_pthread_once_done((once))) \
 			win32_pthread_once((once), (fn)); \
 	} while(0)
 #endif							/* WIN32 */
-- 
2.53.0.windows.2

