From ba4a22746478dceff955943f8e0c10361c86a0b4 Mon Sep 17 00:00:00 2001
From: Sami Imseih <simseih@amazon.com>
Date: Tue, 8 Jul 2025 20:36:47 -0500
Subject: [PATCH 1/1] Improve LWLock tranche name visibility across backends

LWLocks in PostgreSQL are grouped into tranches, with tranche names shown
as the wait_event in pg_stat_activity. Built-in tranche names and those
registered during postmaster startup are inherited by backend processes
via fork(). However, tranche names registered by individual backends using
LWLockRegisterTranche() are stored only in local memory and unknown to
other backends.

This causes inconsistent wait event reporting in multi-backend scenarios,
especially when extensions dynamically register tranche names via APIs
like GetNamedDSHash. Backends unaware of these tranche names fallback to
reporting a generic "extension" name.

This patch improves this situation by storing tranche names registered by
normal backends in shared memory (using a dynamic shared hash table keyed
by tranche ID). Lookup of tranche names now proceeds through built-in names,
local lists, and finally shared memory, improving consistency of wait event
reporting across all backends.

The implementation  preserves the existing LWLockRegisterTranche() API and
maintains the fallback to "extension" if the extension never registers
a tranche name.

There is no removal mechanism for tranche entries in shared memory;
this is deemed acceptable given the typically small number of extensions and
LWLocks involved.
---
 src/backend/storage/ipc/ipci.c                |   6 +
 src/backend/storage/lmgr/lwlock.c             | 191 +++++++++++++++---
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/storage/lwlock.h                  |  15 ++
 src/include/storage/lwlocklist.h              |   1 +
 src/tools/pgindent/typedefs.list              |   1 +
 6 files changed, 190 insertions(+), 25 deletions(-)

diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 2fa045e6b0f..d99224324db 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -150,6 +150,7 @@ CalculateShmemSize(int *num_semaphores)
 	size = add_size(size, InjectionPointShmemSize());
 	size = add_size(size, SlotSyncShmemSize());
 	size = add_size(size, AioShmemSize());
+	size = add_size(size, LWLockTrancheShmemSize());
 
 	/* include additional requested shmem from preload libraries */
 	size = add_size(size, total_addin_request);
@@ -343,6 +344,11 @@ CreateOrAttachShmemStructs(void)
 	WaitEventCustomShmemInit();
 	InjectionPointShmemInit();
 	AioShmemInit();
+
+	/*
+	 * LWLock Named Tranches
+	 */
+	LWLockTrancheShmemInit();
 }
 
 /*
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 46f44bc4511..64291e69e0f 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -127,8 +127,10 @@ StaticAssertDecl((LW_VAL_EXCLUSIVE & LW_FLAG_MASK) == 0,
  * appear in BuiltinTrancheNames[] below.
  *
  * 3. Extensions can create new tranches, via either RequestNamedLWLockTranche
- * or LWLockRegisterTranche.  The names of these that are known in the current
- * process appear in LWLockTrancheNames[].
+ * or LWLockRegisterTranche. Tranche names registered during postmaster startup
+ * are inherited by backends at fork time and stored in LWLockTrancheNames[].
+ * Tranche names registered by a normal backend are tracked in the dshash table
+ * lwlock_tranche_names_dsh, ensuring they are visible to all backends.
  *
  * All these names are user-visible as wait event names, so choose with care
  * ... and do not forget to update the documentation's list of wait events.
@@ -224,6 +226,28 @@ typedef struct NamedLWLockTrancheRequest
 	int			num_lwlocks;
 } NamedLWLockTrancheRequest;
 
+/* struct representing stated for shared named tranches */
+typedef struct LWLockTrancheNamesShared
+{
+	dsa_handle	lwlock_tranche_names_dsa;
+	dshash_table_handle lwlock_tranche_names_dsh;
+
+} LWLockTrancheNamesShared;
+
+/* parameters for shared tranches hash table */
+static const dshash_parameters dsh_params = {
+	sizeof(int),
+	sizeof(LWLockTracheNamesEntry),
+	dshash_memcmp,
+	dshash_memhash,
+	dshash_memcpy,
+	LWTRANCHE_FIRST_USER_DEFINED
+};
+
+static LWLockTrancheNamesShared *NamedLWLockTrancheShared;
+static dsa_area *lwlock_tranche_names_dsa = NULL;
+static dshash_table *lwlock_tranche_names_dsh = NULL;
+
 static NamedLWLockTrancheRequest *NamedLWLockTrancheRequestArray = NULL;
 static int	NamedLWLockTrancheRequestsAllocated = 0;
 
@@ -241,7 +265,8 @@ NamedLWLockTranche *NamedLWLockTrancheArray = NULL;
 static void InitializeLWLocks(void);
 static inline void LWLockReportWaitStart(LWLock *lock);
 static inline void LWLockReportWaitEnd(void);
-static const char *GetLWTrancheName(uint16 trancheId);
+static const char *GetLWTrancheName(int trancheId);
+static void LWLockAttachNamedTranches(void);
 
 #define T_NAME(lock) \
 	GetLWTrancheName((lock)->tranche)
@@ -627,7 +652,10 @@ LWLockNewTrancheId(void)
 }
 
 /*
- * Register a dynamic tranche name in the lookup table of the current process.
+ * Register a dynamic tranche name in the lookup table of the current process
+ * or in shared memory. The former is used if the tranche is registered during
+ * postmaster startup. The latter is used when the tranche is registered by
+ * a normal backend.
  *
  * This routine will save a pointer to the tranche name passed as an argument,
  * so the name should be allocated in a backend-lifetime context
@@ -646,24 +674,48 @@ LWLockRegisterTranche(int tranche_id, const char *tranche_name)
 	/* Convert to array index. */
 	tranche_id -= LWTRANCHE_FIRST_USER_DEFINED;
 
-	/* If necessary, create or enlarge array. */
-	if (tranche_id >= LWLockTrancheNamesAllocated)
+	if (!IsUnderPostmaster)
 	{
-		int			newalloc;
+		/* If necessary, create or enlarge array. */
+		if (tranche_id >= LWLockTrancheNamesAllocated)
+		{
+			int			newalloc;
 
-		newalloc = pg_nextpower2_32(Max(8, tranche_id + 1));
+			newalloc = pg_nextpower2_32(Max(8, tranche_id + 1));
 
-		if (LWLockTrancheNames == NULL)
-			LWLockTrancheNames = (const char **)
-				MemoryContextAllocZero(TopMemoryContext,
-									   newalloc * sizeof(char *));
-		else
-			LWLockTrancheNames =
-				repalloc0_array(LWLockTrancheNames, const char *, LWLockTrancheNamesAllocated, newalloc);
-		LWLockTrancheNamesAllocated = newalloc;
+			if (LWLockTrancheNames == NULL)
+				LWLockTrancheNames = (const char **)
+					MemoryContextAllocZero(TopMemoryContext,
+										   newalloc * sizeof(char *));
+			else
+				LWLockTrancheNames =
+					repalloc0_array(LWLockTrancheNames, const char *, LWLockTrancheNamesAllocated, newalloc);
+			LWLockTrancheNamesAllocated = newalloc;
+		}
+
+		LWLockTrancheNames[tranche_id] = tranche_name;
 	}
+	else
+	{
+		bool		found;
+		LWLockTracheNamesEntry *entry;
+
+		if (strlen(tranche_name) + 1 > LWLOCK_SHARED_TRANCHE_NAME_LEN)
+			ereport(ERROR,
+					(errmsg("Tranche name too long")));
+
+		LWLockAttachNamedTranches();
+
+		entry = dshash_find_or_insert(lwlock_tranche_names_dsh, &tranche_id, &found);
 
-	LWLockTrancheNames[tranche_id] = tranche_name;
+		if (entry)
+		{
+			if (!found)
+				strcpy(entry->trancheName, tranche_name);
+
+			dshash_release_lock(lwlock_tranche_names_dsh, entry);
+		}
+	}
 }
 
 /*
@@ -752,24 +804,44 @@ LWLockReportWaitEnd(void)
  * Return the name of an LWLock tranche.
  */
 static const char *
-GetLWTrancheName(uint16 trancheId)
+GetLWTrancheName(int trancheId)
 {
+	LWLockTracheNamesEntry *entry;
+
 	/* Built-in tranche or individual LWLock? */
 	if (trancheId < LWTRANCHE_FIRST_USER_DEFINED)
 		return BuiltinTrancheNames[trancheId];
 
 	/*
-	 * It's an extension tranche, so look in LWLockTrancheNames[].  However,
-	 * it's possible that the tranche has never been registered in the current
-	 * process, in which case give up and return "extension".
+	 * It's an extension tranche, so look in LWLockTrancheNames[] first, and
+	 * then lwlock_tranche_names_dsh if the tranche was registered by a normal
+	 * backend.
 	 */
 	trancheId -= LWTRANCHE_FIRST_USER_DEFINED;
 
-	if (trancheId >= LWLockTrancheNamesAllocated ||
-		LWLockTrancheNames[trancheId] == NULL)
-		return "extension";
+	if (LWLockTrancheNames[trancheId])
+		return LWLockTrancheNames[trancheId];
+
+	LWLockAttachNamedTranches();
 
-	return LWLockTrancheNames[trancheId];
+	entry = dshash_find(lwlock_tranche_names_dsh, &trancheId, false);
+
+	if (entry)
+	{
+		char	   *name = (char *) palloc(LWLOCK_SHARED_TRANCHE_NAME_LEN);
+
+		Assert(strlen(entry->trancheName) + 1 <= LWLOCK_SHARED_TRANCHE_NAME_LEN);
+		strcpy(name, entry->trancheName);
+		dshash_release_lock(lwlock_tranche_names_dsh, entry);
+
+		return name;
+	}
+
+	/*
+	 * The extension did not register a tranche, so let's return the fallback
+	 * name.
+	 */
+	return "extension";
 }
 
 /*
@@ -2035,3 +2107,72 @@ LWLockHeldByMeInMode(LWLock *lock, LWLockMode mode)
 	}
 	return false;
 }
+
+Size
+LWLockTrancheShmemSize(void)
+{
+	Size		size = 0;
+
+	size = sizeof(LWLockTrancheNamesShared);
+
+	return size;
+}
+
+/* Allocate and initialize LWLock tranche names shared memory */
+void
+LWLockTrancheShmemInit(void)
+{
+	bool		found;
+
+	NamedLWLockTrancheShared = (LWLockTrancheNamesShared *)
+		ShmemInitStruct("LWLock Tranches", LWLockTrancheShmemSize(), &found);
+
+	if (!found)
+	{
+		/* First time through, so initialize */
+		MemSet(NamedLWLockTrancheShared, 0, LWLockTrancheShmemSize());
+		NamedLWLockTrancheShared->lwlock_tranche_names_dsa = DSA_HANDLE_INVALID;
+		NamedLWLockTrancheShared->lwlock_tranche_names_dsh = DSHASH_HANDLE_INVALID;
+	}
+}
+
+void
+LWLockAttachNamedTranches(void)
+{
+	MemoryContext oldcontext;
+
+	/* Quick exit if we already did this. */
+	if (NamedLWLockTrancheShared->lwlock_tranche_names_dsh != DSHASH_HANDLE_INVALID &&
+		lwlock_tranche_names_dsh != NULL)
+		return;
+
+	/* Otherwise, use a lock to ensure only one process creates the table. */
+	LWLockAcquire(LWLockNamedTranchesLock, LW_EXCLUSIVE);
+
+	/* Be sure any local memory allocated by DSA routines is persistent. */
+	oldcontext = MemoryContextSwitchTo(TopMemoryContext);
+
+	if (NamedLWLockTrancheShared->lwlock_tranche_names_dsh == DSHASH_HANDLE_INVALID)
+	{
+		/* Initialize dynamic shared hash table for last-start times. */
+		lwlock_tranche_names_dsa = dsa_create(LWTRANCHE_LAUNCHER_DSA);
+		dsa_pin(lwlock_tranche_names_dsa);
+		dsa_pin_mapping(lwlock_tranche_names_dsa);
+		lwlock_tranche_names_dsh = dshash_create(lwlock_tranche_names_dsa, &dsh_params, NULL);
+
+		/* Store handles in shared memory for other backends to use. */
+		NamedLWLockTrancheShared->lwlock_tranche_names_dsa = dsa_get_handle(lwlock_tranche_names_dsa);
+		NamedLWLockTrancheShared->lwlock_tranche_names_dsh = dshash_get_hash_table_handle(lwlock_tranche_names_dsh);
+	}
+	else if (!lwlock_tranche_names_dsh)
+	{
+		/* Attach to existing dynamic shared hash table. */
+		lwlock_tranche_names_dsa = dsa_attach(NamedLWLockTrancheShared->lwlock_tranche_names_dsa);
+		dsa_pin_mapping(lwlock_tranche_names_dsa);
+		lwlock_tranche_names_dsh = dshash_attach(lwlock_tranche_names_dsa, &dsh_params,
+												 NamedLWLockTrancheShared->lwlock_tranche_names_dsh, NULL);
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+	LWLockRelease(LWLockNamedTranchesLock);
+}
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 4da68312b5f..620cc1f8c97 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -352,6 +352,7 @@ DSMRegistry	"Waiting to read or update the dynamic shared memory registry."
 InjectionPoint	"Waiting to read or update information related to injection points."
 SerialControl	"Waiting to read or update shared <filename>pg_serial</filename> state."
 AioWorkerSubmissionQueue	"Waiting to access AIO worker submission queue."
+LWLockNamedTranches	"Waiting to read or update shared lwlock named tranches state."
 
 #
 # END OF PREDEFINED LWLOCKS (DO NOT CHANGE THIS LINE)
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 08a72569ae5..46ca48b5755 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -18,6 +18,7 @@
 #error "lwlock.h may not be included from frontend code"
 #endif
 
+#include "lib/dshash.h"
 #include "port/atomics.h"
 #include "storage/lwlocknames.h"
 #include "storage/proclist_types.h"
@@ -80,6 +81,16 @@ typedef struct NamedLWLockTranche
 	char	   *trancheName;
 } NamedLWLockTranche;
 
+/* length of a lwlock shared tranche name */
+#define LWLOCK_SHARED_TRANCHE_NAME_LEN  256
+
+/* struct for storing trache information in shared memory */
+typedef struct LWLockTracheNamesEntry
+{
+	int			trancheId;
+	char		trancheName[LWLOCK_SHARED_TRANCHE_NAME_LEN];
+}			LWLockTracheNamesEntry;
+
 extern PGDLLIMPORT NamedLWLockTranche *NamedLWLockTrancheArray;
 extern PGDLLIMPORT int NamedLWLockTrancheRequests;
 
@@ -172,6 +183,10 @@ extern int	LWLockNewTrancheId(void);
 extern void LWLockRegisterTranche(int tranche_id, const char *tranche_name);
 extern void LWLockInitialize(LWLock *lock, int tranche_id);
 
+/* Creates the shared memory state for tracking tranche names */
+extern Size LWLockTrancheShmemSize(void);
+extern void LWLockTrancheShmemInit(void);
+
 /*
  * Every tranche ID less than NUM_INDIVIDUAL_LWLOCKS is reserved; also,
  * we reserve additional tranche IDs for builtin tranches not included in
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index a9681738146..029bed98988 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -84,3 +84,4 @@ PG_LWLOCK(50, DSMRegistry)
 PG_LWLOCK(51, InjectionPoint)
 PG_LWLOCK(52, SerialControl)
 PG_LWLOCK(53, AioWorkerSubmissionQueue)
+PG_LWLOCK(54, LWLockNamedTranches)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 83192038571..c65fa454104 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -4343,3 +4343,4 @@ yyscan_t
 z_stream
 z_streamp
 zic_t
+LWLockTrancheNamesShared
-- 
2.43.0

