From 7d9583f19d019ad65e68e6da4b09d4f1a59a3e45 Mon Sep 17 00:00:00 2001
From: Harrison Booth <harrisontbooth@gmail.com>
Date: Mon, 12 Oct 2026 15:54:29 -0500
Subject: [PATCH v2] Fix races in Windows pthread mutex initialization

Claim initialization only when initstate is zero. Publish state one only
from the initializer, so a waiter cannot overwrite initialized state.

Use an acquire load on the lock fast path and a relaxed load after locking,
avoiding read-modify-write operations on already initialized mutexes.

Keep pthread_once unchanged and preserve the existing type layouts.
---
 src/interfaces/ecpg/ecpglib/misc.c   | 23 +++++++++++++++++------
 src/interfaces/libpq/pthread-win32.c | 23 +++++++++++++++++------
 2 files changed, 34 insertions(+), 12 deletions(-)

diff --git a/src/interfaces/ecpg/ecpglib/misc.c b/src/interfaces/ecpg/ecpglib/misc.c
index 354a2e731fc..09d44164f47 100644
--- a/src/interfaces/ecpg/ecpglib/misc.c
+++ b/src/interfaces/ecpg/ecpglib/misc.c
@@ -436,16 +436,22 @@ pthread_mutex_init(pthread_mutex_t *mp, void *attr)
 int
 pthread_mutex_lock(pthread_mutex_t *mp)
 {
-	/* Initialize the csection if not already done */
-	if (mp->initstate != 1)
+	/* Acquire the initialization state without a read-modify-write. */
+#ifdef _MSC_VER
+	if (ReadAcquire(&mp->initstate) != 1)
+#else
+	if (__atomic_load_n(&mp->initstate, __ATOMIC_ACQUIRE) != 1)
+#endif
 	{
 		LONG		istate;
 
-		while ((istate = InterlockedExchange(&mp->initstate, 2)) == 2)
+		while ((istate = InterlockedCompareExchange(&mp->initstate, 2, 0)) == 2)
 			Sleep(0);			/* wait, another thread is doing this */
-		if (istate != 1)
+		if (istate == 0)
+		{
 			InitializeCriticalSection(&mp->csection);
-		InterlockedExchange(&mp->initstate, 1);
+			InterlockedExchange(&mp->initstate, 1);
+		}
 	}
 	EnterCriticalSection(&mp->csection);
 	return 0;
@@ -454,7 +460,12 @@ pthread_mutex_lock(pthread_mutex_t *mp)
 int
 pthread_mutex_unlock(pthread_mutex_t *mp)
 {
-	if (mp->initstate != 1)
+	/* The successful lock already acquired the initialized critical section. */
+#ifdef _MSC_VER
+	if (ReadNoFence(&mp->initstate) != 1)
+#else
+	if (__atomic_load_n(&mp->initstate, __ATOMIC_RELAXED) != 1)
+#endif
 		return EINVAL;
 	LeaveCriticalSection(&mp->csection);
 	return 0;
diff --git a/src/interfaces/libpq/pthread-win32.c b/src/interfaces/libpq/pthread-win32.c
index cf66284f007..da5d6c9e420 100644
--- a/src/interfaces/libpq/pthread-win32.c
+++ b/src/interfaces/libpq/pthread-win32.c
@@ -41,16 +41,22 @@ pthread_mutex_init(pthread_mutex_t *mp, void *attr)
 int
 pthread_mutex_lock(pthread_mutex_t *mp)
 {
-	/* Initialize the csection if not already done */
-	if (mp->initstate != 1)
+	/* Acquire the initialization state without a read-modify-write. */
+#ifdef _MSC_VER
+	if (ReadAcquire(&mp->initstate) != 1)
+#else
+	if (__atomic_load_n(&mp->initstate, __ATOMIC_ACQUIRE) != 1)
+#endif
 	{
 		LONG		istate;
 
-		while ((istate = InterlockedExchange(&mp->initstate, 2)) == 2)
+		while ((istate = InterlockedCompareExchange(&mp->initstate, 2, 0)) == 2)
 			Sleep(0);			/* wait, another thread is doing this */
-		if (istate != 1)
+		if (istate == 0)
+		{
 			InitializeCriticalSection(&mp->csection);
-		InterlockedExchange(&mp->initstate, 1);
+			InterlockedExchange(&mp->initstate, 1);
+		}
 	}
 	EnterCriticalSection(&mp->csection);
 	return 0;
@@ -59,7 +65,12 @@ pthread_mutex_lock(pthread_mutex_t *mp)
 int
 pthread_mutex_unlock(pthread_mutex_t *mp)
 {
-	if (mp->initstate != 1)
+	/* The successful lock already acquired the initialized critical section. */
+#ifdef _MSC_VER
+	if (ReadNoFence(&mp->initstate) != 1)
+#else
+	if (__atomic_load_n(&mp->initstate, __ATOMIC_RELAXED) != 1)
+#endif
 		return EINVAL;
 	LeaveCriticalSection(&mp->csection);
 	return 0;
