From 7f0704f51ac799b94e129df2d23cdfa255fcc5ae Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.munro@gmail.com>
Date: Fri, 31 Jul 2026 13:32:55 +1200
Subject: [PATCH v3 03/12] port: Native Windows mapping for pg_threads.h.

XXX This would be needed if we are not yet ready to require <threads.h>
on Visual Studio.
---
 src/include/port/pg_threads.h                 |  29 +-
 src/include/port/pg_threads/map_threads_ext.h |   4 +-
 src/include/port/pg_threads/map_windows.h     | 192 +++++++
 src/include/port/pg_threads_ext.h             |   4 +-
 src/port/pg_threads.c                         | 514 +++++++++++++++++-
 5 files changed, 714 insertions(+), 29 deletions(-)
 create mode 100644 src/include/port/pg_threads/map_windows.h

diff --git a/src/include/port/pg_threads.h b/src/include/port/pg_threads.h
index a5df4dc304d..2567251c571 100644
--- a/src/include/port/pg_threads.h
+++ b/src/include/port/pg_threads.h
@@ -13,23 +13,23 @@
  * the same style, most usefully static initializers for mutexes.  This header
  * restricts itself to faithfully copying <threads.h>.
  *
- * Two implementations are available:
+ * Three implementations are available:
  *
- * (1) map_pthread.h for <pthread.h> on POSIX, Windows + MinGW
+ * (1) map_pthread.h for <pthread.h> on POSIX systems
  * (2) map_threads.h for <threads.h> on Windows + Visual Studio
+ * (3) map_windows.h for <windows.h> on Windows
  *
- * The choice is made automatically, but can be overridden for testing with
- * PG_THREADS_USE_THREADS_H.  That works on common POSIX systems, but such
- * builds are intended strictly for testing (see map_threads_ext.h for
- * details).
+ * The choice is made automatically, but can be overridden for testing.
+ * PG_THREADS_USE_THREADS_H works on common POSIX systems, but such builds are
+ * intended strictly for testing (see map_threads_ext.h for details).
  *
  * The following macros are defined to signal known failures to conform to the
  * <threads.h> specification on some systems.  See the implementation headers
  * for details.
  *
- * - PG_MTX_TIMED_NOT_SUPPORTED:		macOS (1)
- * - PG_THRD_CURRENT_NOT_DETACHABLE:	Windows (2)
- * - PG_THRD_CURRENT_NOT_JOINABLE:		Windows (2)
+ * - PG_MTX_TIMED_NOT_SUPPORTED:		macOS (1), Windows (3)
+ * - PG_THRD_CURRENT_NOT_DETACHABLE:	Windows (2 and 3)
+ * - PG_THRD_CURRENT_NOT_JOINABLE:		Windows (2 and 3)
  *
  * https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1548.pdf
  * https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/threads.h.html
@@ -47,11 +47,14 @@
 
 /* select implementation, if not defined manually for testing. */
 #if !defined(PG_THREADS_USE_PTHREAD_H) &&		\
-	!defined(PG_THREADS_USE_THREADS_H)
-#if !defined(_MSC_VER)
+	!defined(PG_THREADS_USE_THREADS_H) &&		\
+	!defined(PG_THREADS_USE_WINDOWS_H)
+#if !defined(WIN32)
 #define PG_THREADS_USE_PTHREAD_H
-#else
+#elif defined(_MSC_VER)
 #define PG_THREADS_USE_THREADS_H
+#else
+#define PG_THREADS_USE_WINDOWS_H
 #endif
 #endif
 
@@ -72,6 +75,8 @@ typedef void (*pg_tss_dtor_t) (void *);
 #include "port/pg_threads/map_pthread.h"
 #elif defined(PG_THREADS_USE_THREADS_H)
 #include "port/pg_threads/map_threads.h"
+#elif defined(PG_THREADS_USE_WINDOWS_H)
+#include "port/pg_threads/map_windows.h"
 #endif
 
 /* standard types with prefix */
diff --git a/src/include/port/pg_threads/map_threads_ext.h b/src/include/port/pg_threads/map_threads_ext.h
index f603ab496bd..b13f0407b11 100644
--- a/src/include/port/pg_threads/map_threads_ext.h
+++ b/src/include/port/pg_threads/map_threads_ext.h
@@ -22,11 +22,11 @@
 #ifndef PG_THREADS_MAP_THREADS_EXT_H
 #define PG_THREADS_MAP_THREADS_EXT_H
 
-#if !defined(_MSC_VER)
+#ifndef WIN32
 #include <pthread.h>
 #endif
 
-#if defined(_MSC_VER)
+#if defined(WIN32)
 /*
  * Visual Studio's <thread.h>: cnd_t and mtx_t can be zero-initialized per
  * published documentation and example code, so this is a supported deployment
diff --git a/src/include/port/pg_threads/map_windows.h b/src/include/port/pg_threads/map_windows.h
new file mode 100644
index 00000000000..051ee6bbab4
--- /dev/null
+++ b/src/include/port/pg_threads/map_windows.h
@@ -0,0 +1,192 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_threads/map_windows.h
+ *    Map pg_threads.h interface to Windows native APIs.
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *    src/include/port/pg_threads/map_windows.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_THREADS_MAP_WINDOWS
+#define PG_THREADS_MAP_WINDOWS
+
+#ifndef PG_THREADS_H
+#error "include pg_threads.h instead"
+#endif
+
+#include <windows.h>
+
+enum
+{
+	pg_thrd_success_impl = 0,
+	pg_thrd_nomem_impl,
+	pg_thrd_timedout_impl,
+	pg_thrd_busy_impl,
+	pg_thrd_error_impl,
+};
+
+/* These need the definition of pg_thrd_success_impl. */
+#include "port/pg_threads/map_windows_error.h"
+#include "port/pg_threads/wrap.h"
+
+/*
+ * Experimental build option: define PG_THRD_CURRENT_CONFORMING to make
+ * pg_thrd_create() a bit slower in exchange for being able to pass its result
+ * to pg_thrd_detach() and (after copying it to another thread somehow)
+ * pg_thrd_join().
+ *
+ * The default is that you can't do those things, and that matches the
+ * behavior of Visual Studio's <threads.h>.
+ *
+ * XXX It doesn't seem that useful to do those things anyway, and unlikely
+ * that anyone could usefully make use of these "anti-feature" macros.  Hence
+ * default behavior of following Visual Studio's non-conforming behavior.  The
+ * option is provided as illustration of the tradeoff being made under the
+ * covers.
+ */
+#ifndef PG_THRD_CURRENT_CONFORMING
+#define PG_THRD_CURRENT_NOT_DETACHABLE
+#define PG_THRD_CURRENT_NOT_JOINABLE
+#endif
+
+/*
+ * pg_mtx_t is mapped to SRWLOCK or CRITICAL_SECTION depending on whether
+ * pg_mtx_recursive is requested.  Neither supports timeouts, so pg_mtx_timed
+ * remains unsupported.
+ *
+ * XXX CreateMutex() could provide third union option that supports timedouts
+ * and recursion, but those doesn't support interaction with
+ * CONDITION_VARIABLE as required for cnd_wait().  That's a more complicated
+ * feature hole, while this feature hole currently coincides with macOS.
+ *
+ * XXX Visual Studio's mtx_t doesn't have this problem, being built from
+ * SRWLOCK + CONDITION_VARIABLE + counters.
+ */
+#define PG_MTX_TIMED_NOT_SUPPORTED
+
+typedef void (*pg_call_once_function_impl) (void);
+
+typedef CONDITION_VARIABLE pg_cnd_impl;
+typedef INIT_ONCE pg_once_flag_impl;
+
+typedef struct pg_mtx_impl
+{
+	enum
+	{
+		PG_MTX_SRWLOCK = 0,
+		PG_MTX_CRITICAL_SECTION,
+	}			type;
+	union
+	{
+		SRWLOCK		srwlock;
+		CRITICAL_SECTION critical_section;
+	};
+} pg_mtx_impl;
+
+typedef struct pg_thrd_impl
+{
+	HANDLE		handle;
+	DWORD		id;
+} pg_thrd_impl;
+
+typedef struct pg_tss_impl
+{
+	int			index;
+	int			generation;
+} pg_tss_impl;
+
+
+enum
+{
+	pg_mtx_plain_impl = 0,
+	pg_mtx_recursive_impl = 1 << 1,
+	pg_mtx_timed_impl = 2 << 2,
+};
+
+#define PG_ONCE_FLAG_INIT_IMPL INIT_ONCE_STATIC_INIT
+#define PG_TSS_DTOR_ITERATIONS_IMPL 4	/* typical POSIX value */
+
+/* cnd_t */
+PG_THREADS_MAP_SUCCESS(pg_cnd_broadcast, WakeAllConditionVariable, pg_cnd_impl *);
+PG_THREADS_EMPTY_VOID(pg_cnd_destroy, pg_cnd_impl *);
+PG_THREADS_MAP_SUCCESS(pg_cnd_init, InitializeConditionVariable, pg_cnd_impl *);
+PG_THREADS_MAP_SUCCESS(pg_cnd_signal, WakeConditionVariable, pg_cnd_impl *);
+PG_THREADS_OUTOFLINE(pg_cnd_timedwait, int, pg_cnd_impl *, pg_mtx_impl *, const struct timespec *);
+PG_THREADS_OUTOFLINE(pg_cnd_wait, int, pg_cnd_impl *, pg_mtx_impl *);
+
+/* mtx_t */
+PG_THREADS_OUTOFLINE_VOID(pg_mtx_destroy, pg_mtx_impl *);
+PG_THREADS_OUTOFLINE(pg_mtx_init, int, pg_mtx_impl *, int);
+
+static inline int
+pg_mtx_lock(pg_mtx_impl *mutex)
+{
+	switch (mutex->type)
+	{
+		case PG_MTX_SRWLOCK:
+			AcquireSRWLockExclusive(&mutex->srwlock);
+			break;
+		case PG_MTX_CRITICAL_SECTION:
+			EnterCriticalSection(&mutex->critical_section);
+			break;
+	}
+	return pg_thrd_success_impl;
+}
+
+PG_THREADS_MAP_ERROR_UNSUPPORTED(pg_mtx_timedlock, pg_mtx_impl *, const struct timespec *);
+
+static inline int
+pg_mtx_trylock(pg_mtx_impl *mutex)
+{
+	switch (mutex->type)
+	{
+		case PG_MTX_SRWLOCK:
+			if (!TryAcquireSRWLockExclusive(&mutex->srwlock))
+				return pg_thrd_busy_impl;
+			break;
+		case PG_MTX_CRITICAL_SECTION:
+			if (!TryEnterCriticalSection(&mutex->critical_section))
+				return pg_thrd_busy_impl;
+			break;
+	}
+	return pg_thrd_success_impl;
+}
+
+static inline int
+pg_mtx_unlock(pg_mtx_impl *mutex)
+{
+	switch (mutex->type)
+	{
+		case PG_MTX_SRWLOCK:
+			ReleaseSRWLockExclusive(&mutex->srwlock);
+			break;
+		case PG_MTX_CRITICAL_SECTION:
+			LeaveCriticalSection(&mutex->critical_section);
+			break;
+	}
+	return pg_thrd_success_impl;
+}
+
+/* once_flag */
+PG_THREADS_OUTOFLINE_VOID(pg_call_once, pg_once_flag_impl *, pg_call_once_function_impl);
+
+/* thrd_t */
+PG_THREADS_OUTOFLINE_0ARG(pg_thrd_current, pg_thrd_impl);
+PG_THREADS_OUTOFLINE(pg_thrd_create, int, pg_thrd_impl *, pg_thrd_start_t, void *);
+PG_THREADS_OUTOFLINE(pg_thrd_detach, int, pg_thrd_impl);
+PG_THREADS_OUTOFLINE(pg_thrd_equal, int, pg_thrd_impl, pg_thrd_impl);
+PG_THREADS_FORWARD_VOID(pg_thrd_exit, ExitThread, int);
+PG_THREADS_OUTOFLINE(pg_thrd_join, int, pg_thrd_impl, int *);
+PG_THREADS_OUTOFLINE(pg_thrd_sleep, int, const struct timespec *, struct timespec *);
+PG_THREADS_FORWARD_VOID_0ARG(pg_thrd_yield, SwitchToThread);
+
+/* tss_t */
+PG_THREADS_OUTOFLINE(pg_tss_create, int, pg_tss_impl *, pg_tss_dtor_t);
+PG_THREADS_OUTOFLINE_VOID(pg_tss_delete, pg_tss_impl);
+PG_THREADS_OUTOFLINE(pg_tss_get, void *, pg_tss_impl);
+PG_THREADS_OUTOFLINE(pg_tss_set, int, pg_tss_impl, void *);
+
+#endif
diff --git a/src/include/port/pg_threads_ext.h b/src/include/port/pg_threads_ext.h
index bebea5dfb09..26f0f2e7552 100644
--- a/src/include/port/pg_threads_ext.h
+++ b/src/include/port/pg_threads_ext.h
@@ -36,10 +36,12 @@
 
 #include "port/pg_threads.h"
 
-#if defined(PG_THREADS_USE_PTHREAD_H)
+#if   defined(PG_THREADS_USE_PTHREAD_H)
 #include "port/pg_threads/map_pthread_ext.h"
 #elif defined(PG_THREADS_USE_THREADS_H)
 #include "port/pg_threads/map_threads_ext.h"
+#elif defined(PG_THREADS_USE_WINDOWS_H)
+#include "port/pg_threads/map_windows_ext.h"
 #endif
 
 typedef pg_rwlock_impl pg_rwlock_t;
diff --git a/src/port/pg_threads.c b/src/port/pg_threads.c
index 061c42b2cf6..07dc7fe0dd2 100644
--- a/src/port/pg_threads.c
+++ b/src/port/pg_threads.c
@@ -34,7 +34,7 @@
  *-------------------------------------------------------------------------
  */
 
-#if defined(PG_THREADS_USE_PTHREAD_H)
+#if defined(PG_THREADS_USE_PTHREAD_H) || defined(PG_THREADS_USE_WINDOWS_H)
 #define PG_THREADS_HAVE_DETAILED_ERRORS
 #endif
 
@@ -125,10 +125,12 @@ pg_thrd_internal_error(const char *detail)
 	return pg_thrd_format_error("%s", detail);
 }
 
-#if defined(WIN32) && defined(PG_THREADS_USE_THREADS_H)
+#ifdef WIN32
 /*
  * Convert last Windows error number to a pg_thrd_XXX error, and record the
- * underlying code in a generic detail message.
+ * underlying code in a generic detail message.  (Places that make multiple
+ * system calls can format a custom error message to report which system call
+ * failed instead of using this generic conversion.)
  */
 static int
 pg_thrd_map_windows_error(DWORD windows_error)
@@ -142,7 +144,8 @@ pg_thrd_map_windows_error(DWORD windows_error)
 		case WAIT_TIMEOUT:
 			return pg_thrd_timedout;
 		default:
-			return pg_thrd_error;
+			return pg_thrd_format_error("Windows error: %u",
+										(unsigned int) windows_error);
 	}
 }
 
@@ -152,7 +155,7 @@ pg_thrd_map_last_windows_error(void)
 {
 	return pg_thrd_map_windows_error(GetLastError());
 }
-#elif defined(PG_THREADS_USE_PTHREAD_H)
+#else
 /*
  * Convert a POSIX error to a pg_thrd_XXX error, and record the underlying OS
  * error in a detail message.
@@ -175,11 +178,58 @@ pg_thrd_map_pthread_error(int pthread_error)
 }
 #endif
 
-#if defined(WIN32) && !defined(PG_THREADS_USE_PTHREAD_H)
+#if defined(PG_THREADS_USE_PTHREAD_H) || \
+	defined(PG_THREADS_USE_WINDOWS_H)
 
 /*-------------------------------------------------------------------------
  *
- * map_windows_ext.h
+ * map_{pthread,windows}.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+static int
+pg_mtx_validate_type(int type)
+{
+	if (type & ~(pg_mtx_plain | pg_mtx_timed | pg_mtx_recursive))
+		return pg_thrd_format_error("pg_mtx_init(): bad type flags %d", type);
+
+#ifdef PG_MTX_TIMED_NOT_SUPPORTED
+	if (type & pg_mtx_timed)
+		return pg_thrd_internal_error("pg_mtx_init(): pg_mtx_timed not supported on this platform");
+#endif
+
+	return pg_thrd_success;
+}
+
+#endif
+
+/*-------------------------------------------------------------------------
+ *
+ * map_{pthread,windows}_ext.h, also reached from map_threads_ext.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+static int
+pg_rwlock_validate_type(int type)
+{
+	if (type & ~(pg_rwlock_plain | pg_rwlock_timed))
+		return pg_thrd_format_error("pg_rwlock_init(): bad type flags %d", type);
+
+#ifdef PG_RWLOCK_TIMED_NOT_SUPPORTED
+	if (type & pg_rwlock_timed)
+		return pg_thrd_internal_error("pg_rwlock_init(): pg_rwlock_timed not supported on this platform");
+#endif
+
+	return pg_thrd_success;
+}
+
+#if defined(WIN32)
+
+/*-------------------------------------------------------------------------
+ *
+ * map_windows_ext.h, also used for map_threads_ext.h on Windows systems
  *
  *-------------------------------------------------------------------------
  */
@@ -197,7 +247,7 @@ pg_rwlock_init_impl(pg_rwlock_impl *lock, int type)
 	return pg_thrd_success_impl;
 }
 
-#elif defined(PG_THREADS_USE_PTHREAD_H)
+#else
 
 /*-------------------------------------------------------------------------
  *
@@ -209,19 +259,455 @@ pg_rwlock_init_impl(pg_rwlock_impl *lock, int type)
 int
 pg_rwlock_init_impl(pg_rwlock_impl *lock, int type)
 {
-	if (type & ~(pg_rwlock_plain | pg_rwlock_timed))
-		return pg_thrd_format_error("pg_rwlock_init(): bad type flags %d", type);
+	int			result = pg_rwlock_validate_type(type);
 
-#ifdef PG_RWLOCK_TIMED_NOT_SUPPORTED
-	if (type & pg_rwlock_timed)
-		return pg_thrd_internal_error("pg_rwlock_init(): pg_rwlock_timed not supported on this platform");
-#endif
+	if (result != pg_thrd_success)
+		return result;
 
 	return pg_thrd_map(pthread_rwlock_init(lock, NULL));
 }
 
 #endif
 
+#if defined(PG_THREADS_USE_WINDOWS_H)
+
+/*-------------------------------------------------------------------------
+ *
+ * map_windows.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+/* Maximum number of active pg_tss_t IDs per process. */
+#define PG_TSS_MAX 16
+
+typedef struct pg_tss_value
+{
+	void	   *value;
+	int			generation;
+} pg_tss_value;
+
+typedef struct pg_tss_slot
+{
+	pg_tss_dtor_t destructor;
+	int			generation;
+	bool		in_use;
+} pg_tss_slot;
+
+static thread_local pg_thrd_t pg_thrd_current_thread_local;
+
+static thread_local pg_tss_value pg_tss_values[PG_TSS_MAX];
+static pg_tss_slot pg_tss_slots[PG_TSS_MAX];
+static DWORD pg_tss_fls = FLS_OUT_OF_INDEXES;
+static pg_mtx_t pg_tss_lock = PG_MTX_PLAIN_INIT;
+
+static BOOL CALLBACK
+pg_call_once_invoke(pg_once_flag *flag, void *parameter, void **context)
+{
+	pg_call_once_function_impl function = parameter;
+
+	function();
+	return true;
+}
+
+void
+pg_call_once_impl(pg_once_flag *flag, pg_call_once_function_impl function)
+{
+	InitOnceExecuteOnce(flag, pg_call_once_invoke, function, NULL);
+}
+
+/* Convert an absolute TIME_UTC time to a millisecond delay. */
+static DWORD
+pg_threads_get_ms_delay(const struct timespec *time)
+{
+	struct timespec now;
+	int64_t		now_ms;
+	int64_t		time_ms;
+	int64_t		wait_ms;
+
+	if (time == NULL)
+		return INFINITE;
+
+	timespec_get(&now, TIME_UTC);
+	now_ms = now.tv_sec * 1000 + now.tv_nsec / 1000000;
+	time_ms = time->tv_sec * 1000 + time->tv_nsec / 1000000;
+	wait_ms = time_ms - now_ms;
+	if (wait_ms < 0)
+		wait_ms = 0;
+	else if (wait_ms > INFINITE)
+		wait_ms = INFINITE;
+	return wait_ms;
+}
+
+int
+pg_cnd_timedwait_impl(pg_cnd_impl *cnd, pg_mtx_impl *mutex, const struct timespec *time)
+{
+	DWORD		wait_ms = pg_threads_get_ms_delay(time);
+
+	switch (mutex->type)
+	{
+		case PG_MTX_SRWLOCK:
+			return pg_thrd_map(SleepConditionVariableSRW(cnd,
+														 &mutex->srwlock,
+														 wait_ms,
+														 0));
+		case PG_MTX_CRITICAL_SECTION:
+		default:
+			return pg_thrd_map(SleepConditionVariableCS(cnd,
+														&mutex->critical_section,
+														wait_ms));
+	}
+}
+
+int
+pg_cnd_wait_impl(pg_cnd_impl *cnd, pg_mtx_impl *mutex)
+{
+	return pg_cnd_timedwait_impl(cnd, mutex, NULL);
+}
+
+typedef struct pg_thrd_start_info
+{
+	pg_thrd_start_t function;
+	void	   *argument;
+#ifdef PG_THRD_CURRENT_CONFORMING
+	pg_thrd_t	self;
+#endif
+} pg_thrd_start_info;
+
+static DWORD CALLBACK
+pg_thrd_invoke(void *argument)
+{
+	pg_thrd_start_info start = *(pg_thrd_start_info *) argument;
+#ifdef PG_THRD_CURRENT_CONFORMING
+	pg_thrd_current_thread_local = start.self;
+#endif
+	free(argument);
+	return start.function(start.argument);
+}
+
+int
+pg_thrd_create_impl(pg_thrd_t *thread, pg_thrd_start_t function, void *argument)
+{
+	pg_thrd_start_info *start;
+	DWORD		flags;
+
+	start = malloc(sizeof(*start));
+	if (start == NULL)
+		return pg_thrd_nomem;
+	start->function = function;
+	start->argument = argument;
+
+#ifdef PG_THRD_CURRENT_CONFORMING
+	flags = CREATE_SUSPENDED;	/* wait for "self" to be stored */
+#else
+	flags = 0;					/* run immediately */
+#endif
+
+	thread->handle = CreateThread(NULL, 0, pg_thrd_invoke, start, flags, 0);
+	if (thread->handle == NULL)
+	{
+		free(start);
+		return pg_thrd_map_windows_error(GetLastError());
+	}
+
+	thread->id = GetThreadId(thread->handle);
+	if (thread->id == 0)
+	{
+		/*
+		 * The only documented failures involve bad handles and rights.
+		 * CreateThread() grants THREAD_ALL_ACCESS, so failure shouldn't be
+		 * possible.  We can't even recover from an error at this point, given
+		 * advice that even suspended threads shouldn't be terminated, and we
+		 * surely can't just leave a thread behind.  Log the error and abort.
+		 */
+		fprintf(stderr,
+				"GetThreadId() unexpectedly failed with error %u\n",
+				(unsigned int) GetLastError());
+		abort();
+	}
+
+#ifdef PG_THRD_CURRENT_CONFORMING
+	start->self = *thread;
+	if (!ResumeThread(thread->handle))
+	{
+		/* Previous comment applies here too. */
+		fprintf(stderr,
+				"ResumeThread() unexpectedly failed with error %u\n",
+				(unsigned int) GetLastError());
+		abort();
+	}
+#endif
+
+	return pg_thrd_success;
+}
+
+
+pg_thrd_t
+pg_thrd_current_impl(void)
+{
+	if (pg_thrd_current_thread_local.id == 0)
+	{
+		/*
+		 * Alien thread, or result of pg_thrd_current (and
+		 * PG_THRD_CURRENT_CONFORMING is not defined).  Populate just the
+		 * thread ID on first call from this thread.  Result cannot be used to
+		 * join or detach.
+		 */
+		pg_thrd_current_thread_local.id = GetCurrentThreadId();
+	}
+
+	return pg_thrd_current_thread_local;
+}
+
+int
+pg_thrd_join_impl(pg_thrd_t thread, int *result)
+{
+	if (thread.handle == NULL)
+	{
+		/*
+		 * This thread value must have come from pg_thrd_current() (and
+		 * PG_THRD_CURRENT_CONFORMING is not defined, or it was called from
+		 * an alien thread not created by pg_thrd_create()).
+		 *
+		 * We can't just use OpenThread(thread.id) to get a handle.  We'd have
+		 * to trust the caller to hold a handle somewhere else and not close
+		 * it concurrently, and then close it later to avoid leaking kernel
+		 * resources.  That doesn't seem workable.
+		 */
+		return pg_thrd_format_error("pg_thrd_join(): no handle for thread %u",
+									(unsigned int) thread.id);
+	}
+
+	if (WaitForSingleObject(thread.handle, INFINITE) == WAIT_OBJECT_0)
+	{
+		if (result)
+		{
+			DWORD		exit_value;
+
+			/*
+			 * All bits survive casting through the Windows exit value type.
+			 * In practice, only the sign differs, and you can't join alien
+			 * threads in this implementation (see above).
+			 */
+			static_assert(sizeof(*result) <= sizeof(exit_value),
+						  "thread exit value type is not big enough");
+
+			if (!GetExitCodeThread(thread.handle, &exit_value))
+				return pg_thrd_format_error("pg_thrd_join(): GetExitCodeThread() failed with error %u",
+											(unsigned int) GetLastError());
+
+			if (result)
+				*result = exit_value;
+		}
+		return pg_thrd_map(CloseHandle(thread.handle));
+	}
+	return pg_thrd_format_error("pg_thrd_join(): WaitForSingleObject() failed with error %u",
+								(unsigned int) GetLastError());
+}
+
+int
+pg_thrd_equal_impl(pg_thrd_t lhs, pg_thrd_t rhs)
+{
+	return lhs.id == rhs.id;
+}
+
+int
+pg_thrd_detach_impl(pg_thrd_t thread)
+{
+	if (thread.handle == NULL)
+	{
+		/*
+		 * See also pg_thrd_join_impl().
+		 *
+		 * Windows threads are detached by closing all open handles, but we
+		 * don't know anything about the handles of alien threads, or the
+		 * result of pg_thrd_current() (unless PG_THRD_CURRENT_CONFORMING is
+		 * defined).
+		 */
+		return pg_thrd_format_error("pg_thrd_detach(): no handle for thread %u",
+									(unsigned int) thread.id);
+	}
+
+	return pg_thrd_map(CloseHandle(thread.handle));
+}
+
+int
+pg_thrd_sleep_impl(const struct timespec *duration, struct timespec *remaining)
+{
+	long long	ms = duration->tv_sec * 1000 + duration->tv_nsec / 1000000;
+
+	if (ms < 0)
+		ms = 0;
+	if (ms > INFINITE)
+		ms = INFINITE;
+
+	Sleep(ms);
+
+	return 0;
+}
+
+int
+pg_mtx_init_impl(pg_mtx_impl *mutex, int type)
+{
+	int			result = pg_mtx_validate_type(type);
+
+	if (result != pg_thrd_success)
+		return result;
+
+	if (type & pg_mtx_recursive)
+	{
+		mutex->type = PG_MTX_CRITICAL_SECTION;
+		InitializeCriticalSection(&mutex->critical_section);
+	}
+	else
+	{
+		mutex->type = PG_MTX_SRWLOCK;
+		InitializeSRWLock(&mutex->srwlock);
+	}
+	return pg_thrd_success;
+}
+
+void
+pg_mtx_destroy_impl(pg_mtx_impl *mutex)
+{
+	if (mutex->type == PG_MTX_CRITICAL_SECTION)
+		DeleteCriticalSection(&mutex->critical_section);
+}
+
+/*
+ * Windows own FLS destructors don't have the right semantics to use directly
+ * (among other problems, they call destructors on all values from all
+ * threads, from the wrong thread, when you delete IDs).  We make our own
+ * destructor table and per-thread value array to implement the standard
+ * semantics.
+ */
+static void CALLBACK
+pg_tss_fls_destructor(void *data)
+{
+	for (int iter = 0; iter < PG_TSS_DTOR_ITERATIONS_IMPL; ++iter)
+	{
+		bool		called_destructor = false;
+
+		for (int i = 0; i < lengthof(pg_tss_values); ++i)
+		{
+			void	   *value = pg_tss_values[i].value;
+			pg_tss_dtor_t destructor = NULL;
+
+			if (value == NULL)
+				continue;
+
+			pg_tss_values[i].value = NULL;
+
+			pg_mtx_lock(&pg_tss_lock);
+			if (pg_tss_slots[i].in_use &&
+				pg_tss_slots[i].generation == pg_tss_values[i].generation)
+				destructor = pg_tss_slots[i].destructor;
+			pg_mtx_unlock(&pg_tss_lock);
+
+			if (destructor)
+			{
+				called_destructor = true;
+				destructor(value);
+			}
+		}
+
+		if (!called_destructor)
+			break;
+	}
+}
+
+static bool
+pg_tss_ensure_fls_registered(void)
+{
+	bool		success;
+
+	if (pg_tss_fls != FLS_OUT_OF_INDEXES)
+		return true;			/* fast exit, already set */
+
+	pg_mtx_lock(&pg_tss_lock);
+	if (pg_tss_fls != FLS_OUT_OF_INDEXES)
+	{
+		success = true;
+	}
+	else
+	{
+		pg_tss_fls = FlsAlloc(pg_tss_fls_destructor);
+		if (pg_tss_fls != FLS_OUT_OF_INDEXES)
+			success = true;
+	}
+	pg_mtx_unlock(&pg_tss_lock);
+
+	return success;
+}
+
+int
+pg_tss_create_impl(pg_tss_t *id, pg_tss_dtor_t dtor)
+{
+	bool		created = false;
+
+	if (!pg_tss_ensure_fls_registered())
+		return pg_thrd_map_windows_error(GetLastError());
+
+	pg_mtx_lock(&pg_tss_lock);
+	for (int i = 0; i < lengthof(pg_tss_slots); ++i)
+	{
+		if (!pg_tss_slots[i].in_use)
+		{
+			id->generation = ++pg_tss_slots[i].generation;
+			id->index = i;
+			pg_tss_slots[i].destructor = dtor;
+			pg_tss_slots[i].in_use = true;
+			created = true;
+			break;
+		}
+	}
+	pg_mtx_unlock(&pg_tss_lock);
+
+	if (!created)
+		return pg_thrd_format_error("pg_tss_create(): limit of %d IDs exceeded",
+									PG_TSS_MAX);
+
+	return pg_thrd_success;
+}
+
+void
+pg_tss_delete_impl(pg_tss_t id)
+{
+	pg_mtx_lock(&pg_tss_lock);
+	pg_tss_slots[id.index].in_use = false;
+	pg_mtx_unlock(&pg_tss_lock);
+}
+
+int
+pg_tss_set_impl(pg_tss_t id, void *value)
+{
+	/*
+	 * The generation prevents destructors from being called with leftover
+	 * junk values if id is deleted and then a new one is created with the
+	 * same index.
+	 */
+	pg_tss_values[id.index].value = value;
+	pg_tss_values[id.index].generation = id.generation;
+
+	/* Dummy value so that pg_tss_fls_destructor() runs in this thread. */
+	FlsSetValue(pg_tss_fls, (void *) 1);
+
+	return pg_thrd_success;
+}
+
+void *
+pg_tss_get_impl(pg_tss_t id)
+{
+	/*
+	 * pg_tss_{get,set} seem like candidates for inlining, but for now we keep
+	 * all pg_tss_t functions out-of-line to avoid potential problems with
+	 * cross-DLL thread-locals (not investigated).
+	 */
+	return pg_tss_values[id.index].value;
+}
+
+#endif							/* PG_THREAD_USE_WINDOWS_H */
+
 #ifdef PG_THREADS_USE_PTHREAD_H
 
 /*-------------------------------------------------------------------------
-- 
2.55.0

