From f97d673f9b1aee1626b0b3c702e4406f0c140979 Mon Sep 17 00:00:00 2001
From: Jakub Wartak <jakub.wartak@enterprisedb.com>
Date: Tue, 8 Sep 2026 12:44:55 +0200
Subject: [PATCH v4 04/21] port: Detect mutex misuse in assertion-enabled
 builds.

In assertion-enabled builds, make pg_mtx_init() create
error-checking mutexes when plain ones are requested, and use the
glibc error-checking static initializer for PG_MTX_PLAIN_INIT where
it exists.  Then assert in pg_mtx_lock() and pg_mtx_unlock() that the
operation succeeded.  Few callers check those results, so without
the assertions an error-checking mutex would turn a self-deadlock
into a silently ignored EDEADLK rather than a crash with a stack
trace.

Only the pthread implementation is affected.  Windows SRWLOCKs can't
detect these mistakes, and statically initialized mutexes stay
unchecked on non-glibc systems.

Author: Jakub Wartak <jakub.wartak@enterprisedb.com>
Co-authored-by: Greg Burd <greg@burd.me>
Discussion: https://postgr.es/m/CAKZiRmw3gpoXeOc9Pa_PRpfp-bTeQhg6gaY2Jw0Ck6NX4PWPhQ%40mail.gmail.com
---
 src/include/port/pg_threads/map_pthread.h     | 29 +++++++++++++++++++
 src/include/port/pg_threads/map_pthread_ext.h |  5 ++++
 src/port/pg_threads.c                         |  4 +++
 3 files changed, 38 insertions(+)

diff --git a/src/include/port/pg_threads/map_pthread.h b/src/include/port/pg_threads/map_pthread.h
index ccb5a2f906f..2a7e57e96f9 100644
--- a/src/include/port/pg_threads/map_pthread.h
+++ b/src/include/port/pg_threads/map_pthread.h
@@ -80,14 +80,43 @@ PG_THREADS_MAP(pg_cnd_wait, pthread_cond_wait, pg_cnd_impl *, pg_mtx_impl *);
 /* mtx_t */
 PG_THREADS_FORWARD_VOID(pg_mtx_destroy, pthread_mutex_destroy, pg_mtx_impl *);
 PG_THREADS_OUTOFLINE(pg_mtx_init, int, pg_mtx_impl *, int);
+#ifndef USE_ASSERT_CHECKING
 PG_THREADS_MAP(pg_mtx_lock, pthread_mutex_lock, pg_mtx_impl *);
+#else
+/*
+ * In assertion-enabled builds, pg_mtx_init() and PG_MTX_PLAIN_INIT ask for
+ * error-checking mutexes where available.  Callers rarely check the result
+ * of locking and unlocking, so assert success here instead, turning a
+ * self-deadlock or an unlock by the wrong thread into a crash with a stack
+ * trace.
+ */
+static inline int
+pg_mtx_lock(pg_mtx_impl *mtx)
+{
+	int			result = pthread_mutex_lock(mtx);
+
+	Assert(result == 0);
+	return pg_thrd_map(result);
+}
+#endif
 #ifndef PG_MTX_TIMED_NOT_SUPPORTED
 PG_THREADS_MAP(pg_mtx_timedlock, pthread_mutex_timedlock, pg_mtx_impl *, const struct timespec *);
 #else
 PG_THREADS_MAP_ERROR_UNSUPPORTED(pg_mtx_timedlock, pg_mtx_impl *, const struct timespec *);
 #endif
 PG_THREADS_MAP(pg_mtx_trylock, pthread_mutex_trylock, pg_mtx_impl *);
+#ifndef USE_ASSERT_CHECKING
 PG_THREADS_MAP(pg_mtx_unlock, pthread_mutex_unlock, pg_mtx_impl *);
+#else
+static inline int
+pg_mtx_unlock(pg_mtx_impl *mtx)
+{
+	int			result = pthread_mutex_unlock(mtx);
+
+	Assert(result == 0);
+	return pg_thrd_map(result);
+}
+#endif
 
 /* once_flag */
 PG_THREADS_FORWARD_VOID(pg_call_once, pthread_once, pg_once_flag_impl *, pg_call_once_function_impl);
diff --git a/src/include/port/pg_threads/map_pthread_ext.h b/src/include/port/pg_threads/map_pthread_ext.h
index 4ebdc6bb5f6..d3d53d0d138 100644
--- a/src/include/port/pg_threads/map_pthread_ext.h
+++ b/src/include/port/pg_threads/map_pthread_ext.h
@@ -45,8 +45,13 @@ enum
 
 #ifndef PG_THREADS_USE_THREADS_H
 #define PG_CND_INIT_IMPL			PTHREAD_COND_INITIALIZER
+#if defined(USE_ASSERT_CHECKING) && defined(PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP)
+/* A glibc extension.  Elsewhere, static mutexes don't check for misuse. */
+#define PG_MTX_PLAIN_INIT_IMPL		PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP
+#else
 #define PG_MTX_PLAIN_INIT_IMPL		PTHREAD_MUTEX_INITIALIZER
 #endif
+#endif
 #define PG_RWLOCK_PLAIN_INIT_IMPL	PTHREAD_RWLOCK_INITIALIZER
 
 /* pg_rwlock_t */
diff --git a/src/port/pg_threads.c b/src/port/pg_threads.c
index 922060a2d5e..173d6a574fb 100644
--- a/src/port/pg_threads.c
+++ b/src/port/pg_threads.c
@@ -361,6 +361,10 @@ pg_mtx_init_impl(pg_mtx_impl *mtx, int type)
 	pthread_mutexattr_init(&attr);
 	if (type & pg_mtx_recursive_impl)
 		pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
+#ifdef USE_ASSERT_CHECKING
+	else
+		pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);
+#endif
 	result = pg_thrd_map(pthread_mutex_init(mtx, &attr));
 	pthread_mutexattr_destroy(&attr);
 
-- 
2.54.0

