From e8a507f75b8402bb8f452734df70c38973b0efdc Mon Sep 17 00:00:00 2001
From: Sami Imseih <simseih@amazon.com>
Date: Mon, 21 Jul 2025 19:02:47 -0500
Subject: [PATCH v6 1/1] Implement a DSA for LWLock tranche names

Previously, only LWLock tranches registered during postmaster startup were
available to all backends via a tranche name array inherited through fork.
Other backends that wanted to know about a tranche name had to register it
using LWLockRegisterTranche. While this design worked as intended, it was
not ideal because if a backend did not call LWLockRegisterTranche, it would
show a generic "extension" wait_event name in pg_stat_activity instead of
the actual tranche name. This caused inconsistencies with other backends
that registered the tranche name.

Commit fe07100e82b0 made dynamic creation of named DSA areas (and dshash)
much simpler for a backend, so it is anticipated that fewer tranche names
will be registered during postmaster startup in the future, with more
registered dynamically. This will make the above issue more common.

To address this, this change introduces a DSA to store LWLock tranche names
after postmaster startup. When a backend looks up a tranche name by tranche ID,
it first checks its local cache; if the name is not found locally, it fetches
it from the DSA and caches it for future lookups.

This also simplifies the API for registering tranche names: all registration
work is now done via LWLockNewTrancheId, which accepts a tranche name. The
LWLockRegisterTranche call is no longer required.

Additionally, while users should not pass arbitrary tranche IDs (that is,
IDs not created via LWLockNewTrancheId) to LWLockInitialize, nothing
technically prevents them from doing so. Therefore, we must continue to
handle such cases gracefully by returning a default "extension" tranche name.

Discussion: https://www.postgresql.org/message-id/flat/CAA5RZ0vvED3naph8My8Szv6DL4AxOVK3eTPS0qXsaKi%3DbVdW2A%40mail.gmail.com
---
 contrib/pg_prewarm/autoprewarm.c              |   4 +-
 doc/src/sgml/xfunc.sgml                       |  20 +-
 src/backend/storage/ipc/dsm_registry.c        |  16 +-
 src/backend/storage/ipc/ipci.c                |   2 +
 src/backend/storage/lmgr/lwlock.c             | 416 +++++++++++++++---
 src/backend/utils/activity/wait_event.c       |   3 -
 .../utils/activity/wait_event_names.txt       |   2 +
 src/backend/utils/init/postinit.c             |   2 +
 src/include/storage/lwlock.h                  |  46 +-
 src/include/storage/lwlocklist.h              |   2 +
 src/include/utils/wait_classes.h              |   2 +
 src/test/modules/Makefile                     |   3 +-
 src/test/modules/meson.build                  |   1 +
 src/test/modules/test_dsa/test_dsa.c          |   6 +-
 .../test_dsm_registry/test_dsm_registry.c     |   3 +-
 .../modules/test_lwlock_tranches/Makefile     |  25 ++
 .../modules/test_lwlock_tranches/meson.build  |  36 ++
 .../t/001_test_lwlock_tranches.pl             | 115 +++++
 .../test_lwlock_tranches--1.0.sql             |  16 +
 .../test_lwlock_tranches.c                    | 144 ++++++
 .../test_lwlock_tranches.control              |   6 +
 .../modules/test_radixtree/test_radixtree.c   |   9 +-
 src/test/modules/test_slru/test_slru.c        |   6 +-
 .../modules/test_tidstore/test_tidstore.c     |   3 +-
 24 files changed, 770 insertions(+), 118 deletions(-)
 create mode 100644 src/test/modules/test_lwlock_tranches/Makefile
 create mode 100644 src/test/modules/test_lwlock_tranches/meson.build
 create mode 100644 src/test/modules/test_lwlock_tranches/t/001_test_lwlock_tranches.pl
 create mode 100644 src/test/modules/test_lwlock_tranches/test_lwlock_tranches--1.0.sql
 create mode 100644 src/test/modules/test_lwlock_tranches/test_lwlock_tranches.c
 create mode 100644 src/test/modules/test_lwlock_tranches/test_lwlock_tranches.control

diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index c01b9c7e6a4..b623589b430 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -864,7 +864,7 @@ apw_init_state(void *ptr)
 {
 	AutoPrewarmSharedState *state = (AutoPrewarmSharedState *) ptr;
 
-	LWLockInitialize(&state->lock, LWLockNewTrancheId());
+	LWLockInitialize(&state->lock, LWLockNewTrancheId("autoprewarm"));
 	state->bgworker_pid = InvalidPid;
 	state->pid_using_dumpfile = InvalidPid;
 }
@@ -883,8 +883,6 @@ apw_init_shmem(void)
 								   sizeof(AutoPrewarmSharedState),
 								   apw_init_state,
 								   &found);
-	LWLockRegisterTranche(apw_state->lock.tranche, "autoprewarm");
-
 	return found;
 }
 
diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 30219f432d9..32ea6c87be8 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -3758,9 +3758,10 @@ LWLockPadded *GetNamedLWLockTranche(const char *tranche_name)
       There is another, more flexible method of obtaining LWLocks that can be
       done after server startup and outside a
       <literal>shmem_request_hook</literal>.  To do so, first allocate a
-      <literal>tranche_id</literal> by calling:
+      <literal>tranche_id</literal> with an associated <literal>tranche_name</literal>
+      by calling:
 <programlisting>
-int LWLockNewTrancheId(void)
+int LWLockNewTrancheId(const char *tranche_name)
 </programlisting>
       Next, initialize each LWLock, passing the new
       <literal>tranche_id</literal> as an argument:
@@ -3778,17 +3779,16 @@ void LWLockInitialize(LWLock *lock, int tranche_id)
      </para>
 
      <para>
-      Finally, each backend using the <literal>tranche_id</literal> should
-      associate it with a <literal>tranche_name</literal> by calling:
-<programlisting>
-void LWLockRegisterTranche(int tranche_id, const char *tranche_name)
-</programlisting>
+      It is possible to use a <literal>tranche_id</literal> that was not retrieved
+      using <function>LWLockNewTrancheId</function>, but this is not recommended.
+      The ID may clash with an already registered tranche name, or the specified
+      name may not be found. In such cases, looking up the name will return a generic
+      "extension" tranche name.
      </para>
 
      <para>
-      A complete usage example of <function>LWLockNewTrancheId</function>,
-      <function>LWLockInitialize</function>, and
-      <function>LWLockRegisterTranche</function> can be found in
+      A complete usage example of <function>LWLockNewTrancheId</function> and
+      <function>LWLockInitialize</function> can be found in
       <filename>contrib/pg_prewarm/autoprewarm.c</filename> in the
       <productname>PostgreSQL</productname> source tree.
      </para>
diff --git a/src/backend/storage/ipc/dsm_registry.c b/src/backend/storage/ipc/dsm_registry.c
index 1682cc6d34c..cc0adc19971 100644
--- a/src/backend/storage/ipc/dsm_registry.c
+++ b/src/backend/storage/ipc/dsm_registry.c
@@ -307,9 +307,8 @@ GetNamedDSA(const char *name, bool *found)
 		entry->type = DSMR_ENTRY_TYPE_DSA;
 
 		/* Initialize the LWLock tranche for the DSA. */
-		state->tranche = LWLockNewTrancheId();
 		strcpy(state->tranche_name, name);
-		LWLockRegisterTranche(state->tranche, state->tranche_name);
+		state->tranche = LWLockNewTrancheId(state->tranche_name);
 
 		/* Initialize the DSA. */
 		ret = dsa_create(state->tranche);
@@ -330,9 +329,6 @@ GetNamedDSA(const char *name, bool *found)
 			ereport(ERROR,
 					(errmsg("requested DSA already attached to current process")));
 
-		/* Initialize existing LWLock tranche for the DSA. */
-		LWLockRegisterTranche(state->tranche, state->tranche_name);
-
 		/* Attach to existing DSA. */
 		ret = dsa_attach(state->handle);
 		dsa_pin_mapping(ret);
@@ -389,14 +385,12 @@ GetNamedDSHash(const char *name, const dshash_parameters *params, bool *found)
 		entry->type = DSMR_ENTRY_TYPE_DSH;
 
 		/* Initialize the LWLock tranche for the DSA. */
-		dsa_state->tranche = LWLockNewTrancheId();
 		sprintf(dsa_state->tranche_name, "%s%s", name, DSMR_DSA_TRANCHE_SUFFIX);
-		LWLockRegisterTranche(dsa_state->tranche, dsa_state->tranche_name);
+		dsa_state->tranche = LWLockNewTrancheId(dsa_state->tranche_name);
 
 		/* Initialize the LWLock tranche for the dshash table. */
-		dsh_state->tranche = LWLockNewTrancheId();
 		strcpy(dsh_state->tranche_name, name);
-		LWLockRegisterTranche(dsh_state->tranche, dsh_state->tranche_name);
+		dsh_state->tranche = LWLockNewTrancheId(dsh_state->tranche_name);
 
 		/* Initialize the DSA for the hash table. */
 		dsa = dsa_create(dsa_state->tranche);
@@ -427,10 +421,6 @@ GetNamedDSHash(const char *name, const dshash_parameters *params, bool *found)
 			ereport(ERROR,
 					(errmsg("requested DSHash already attached to current process")));
 
-		/* Initialize existing LWLock tranches for the DSA and dshash table. */
-		LWLockRegisterTranche(dsa_state->tranche, dsa_state->tranche_name);
-		LWLockRegisterTranche(dsh_state->tranche, dsh_state->tranche_name);
-
 		/* Attach to existing DSA for the hash table. */
 		dsa = dsa_attach(dsa_state->handle);
 		dsa_pin_mapping(dsa);
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 2fa045e6b0f..c8e4d55cf80 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -343,6 +343,8 @@ CreateOrAttachShmemStructs(void)
 	WaitEventCustomShmemInit();
 	InjectionPointShmemInit();
 	AioShmemInit();
+
+	LWLockTrancheNamesShmemInit();
 }
 
 /*
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index ec9c345ffdf..80d8514c364 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -80,10 +80,12 @@
 #include "pg_trace.h"
 #include "pgstat.h"
 #include "port/pg_bitutils.h"
+#include "storage/ipc.h"
 #include "storage/proc.h"
 #include "storage/proclist.h"
 #include "storage/procnumber.h"
 #include "storage/spin.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 
 #ifdef LWLOCK_STATS
@@ -125,9 +127,12 @@ StaticAssertDecl((LW_VAL_EXCLUSIVE & LW_FLAG_MASK) == 0,
  * 2. There are some predefined tranches for built-in groups of locks defined
  * in lwlocklist.h.  We absorb the names of these tranches, too.
  *
- * 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[].
+ * 3. Extensions can create new tranches using either RequestNamedLWLockTranche
+ * or LWLockNewTrancheId. Tranche names are registered in LWLockTrancheNames in
+ * two ways: if registration happens during postmaster startup, the names are
+ * stored directly in the local array; if registration happens afterward, the
+ * names are first stored in shared memory and later cached into the local array
+ * during lookup.
  *
  * 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.
@@ -144,14 +149,6 @@ StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
 				 LWTRANCHE_FIRST_USER_DEFINED,
 				 "missing entries in BuiltinTrancheNames[]");
 
-/*
- * This is indexed by tranche ID minus LWTRANCHE_FIRST_USER_DEFINED, and
- * stores the names of all dynamically-created tranches known to the current
- * process.  Any unused entries in the array will contain NULL.
- */
-static const char **LWLockTrancheNames = NULL;
-static int	LWLockTrancheNamesAllocated = 0;
-
 /*
  * This points to the main array of LWLocks in shared memory.  Backends inherit
  * the pointer by fork from the postmaster (except in the EXEC_BACKEND case,
@@ -187,6 +184,52 @@ typedef struct NamedLWLockTrancheRequest
 static NamedLWLockTrancheRequest *NamedLWLockTrancheRequestArray = NULL;
 static int	NamedLWLockTrancheRequestsAllocated = 0;
 
+typedef struct LWLockTrancheNamesShmem
+{
+	/* pointer to the DSA shared memory, created in-place */
+	void	   *raw_dsa_area;
+	/* DSA Pointer to a list of DSA pointers, each storing a tranche name */
+	dsa_pointer list_ptr;
+
+	/*
+	 * The number of slots allocated for tranche names. The allocated size
+	 * will grow geometrically to reduce resizing of the list_ptr.
+	 */
+	int			allocated;
+
+	/*
+	 * The highest tranche index currently in use. This is used to determine
+	 * whether local memory can be synchronized with shared memory.
+	 */
+	int			max_tranche_index;
+	/* lock to protect access to the list */
+	LWLock		lock;
+}			LWLockTrancheNamesShmem;
+
+typedef struct LWLockTrancheNamesStruct
+{
+	/*
+	 * Tranche names allocated in shared memory after postmaster startup, when
+	 * dynamic allocation is allowed.
+	 */
+	LWLockTrancheNamesShmem *shmem;
+	dsa_area   *dsa;
+
+	/*
+	 * Tranche names stored in local backend memory, either during postmaster
+	 * startup or as a local cache afterward.
+	 */
+	const char **local;
+	int			allocated;
+}			LWLockTrancheNamesStruct;
+
+static LWLockTrancheNamesStruct LWLockTrancheNames =
+{
+	NULL, NULL, NULL, 0
+};
+
+#define LWLOCK_TRANCHE_NAMES_INIT_SIZE 16
+
 /*
  * NamedLWLockTrancheRequests is both the valid length of the request array,
  * and the length of the shared-memory NamedLWLockTrancheArray later on.
@@ -201,7 +244,11 @@ NamedLWLockTranche *NamedLWLockTrancheArray = NULL;
 static void InitializeLWLocks(void);
 static inline void LWLockReportWaitStart(LWLock *lock);
 static inline void LWLockReportWaitEnd(void);
+static void SetLocalTrancheName(int tranche_index, const char *tranche_name);
+static void SetSharedTrancheName(int tranche_index, const char *tranche_name);
 static const char *GetLWTrancheName(uint16 trancheId);
+static void LWLockTrancheNamesAttach(void);
+static void LWLockTrancheNamesDetach(void);
 
 #define T_NAME(lock) \
 	GetLWTrancheName((lock)->tranche)
@@ -448,11 +495,6 @@ CreateLWLocks(void)
 		/* Initialize all LWLocks */
 		InitializeLWLocks();
 	}
-
-	/* Register named extension LWLock tranches in the current process. */
-	for (int i = 0; i < NamedLWLockTrancheRequests; i++)
-		LWLockRegisterTranche(NamedLWLockTrancheArray[i].trancheId,
-							  NamedLWLockTrancheArray[i].trancheName);
 }
 
 /*
@@ -513,7 +555,7 @@ InitializeLWLocks(void)
 			name = trancheNames;
 			trancheNames += strlen(request->tranche_name) + 1;
 			strcpy(name, request->tranche_name);
-			tranche->trancheId = LWLockNewTrancheId();
+			tranche->trancheId = LWLockNewTrancheId(name);
 			tranche->trancheName = name;
 
 			for (j = 0; j < request->num_lwlocks; j++, lock++)
@@ -569,61 +611,155 @@ GetNamedLWLockTranche(const char *tranche_name)
 }
 
 /*
- * Allocate a new tranche ID.
+ * Allocate a new tranche ID and register it with the associated tranche
+ * name.
+ *
+ * The tranche name will be user-visible as a wait event name, so try to
+ * use a name that fits the style for those.
  */
 int
-LWLockNewTrancheId(void)
+LWLockNewTrancheId(const char *tranche_name)
 {
-	int			result;
+	int			tranche_id;
+	int			tranche_index;
 	int		   *LWLockCounter;
 
 	LWLockCounter = (int *) ((char *) MainLWLockArray - sizeof(int));
 	/* We use the ShmemLock spinlock to protect LWLockCounter */
 	SpinLockAcquire(ShmemLock);
-	result = (*LWLockCounter)++;
+	tranche_id = (*LWLockCounter)++;
 	SpinLockRelease(ShmemLock);
 
-	return result;
+	tranche_index = tranche_id - LWTRANCHE_FIRST_USER_DEFINED;
+
+	if (!IsUnderPostmaster)
+		SetLocalTrancheName(tranche_index, tranche_name);
+	else
+		SetSharedTrancheName(tranche_index, tranche_name);
+
+	return tranche_id;
 }
 
-/*
- * Register a dynamic tranche name in the lookup table of the current process.
- *
- * 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
- * (shared memory, TopMemoryContext, static constant, or similar).
- *
- * The tranche name will be user-visible as a wait event name, so try to
- * use a name that fits the style for those.
- */
-void
-LWLockRegisterTranche(int tranche_id, const char *tranche_name)
+static void
+SetSharedTrancheName(int tranche_index, const char *tranche_name)
 {
-	/* This should only be called for user-defined tranches. */
-	if (tranche_id < LWTRANCHE_FIRST_USER_DEFINED)
-		return;
+	dsa_pointer *name_ptrs;
+	dsa_pointer str_ptr;
+	char	   *str_addr;
+	int			len;
+	int			current_allocated;
+#ifdef USE_INJECTION_POINTS
+	LWLockTranchesInjectionPoint condition = {SET_SHARED, false, NULL, 0, false, false};
+#endif
 
-	/* Convert to array index. */
-	tranche_id -= LWTRANCHE_FIRST_USER_DEFINED;
+	LWLockAcquire(&LWLockTrancheNames.shmem->lock, LW_EXCLUSIVE);
 
-	/* If necessary, create or enlarge array. */
-	if (tranche_id >= LWLockTrancheNamesAllocated)
+	current_allocated = LWLockTrancheNames.shmem->allocated;
+
+	/* Tranche array does not exist, initialize it */
+	if (!DsaPointerIsValid(LWLockTrancheNames.shmem->list_ptr))
 	{
-		int			newalloc;
+		int			init_alloc = LWLOCK_TRANCHE_NAMES_INIT_SIZE;
+
+		LWLockTrancheNames.shmem->allocated = init_alloc;
+		LWLockTrancheNames.shmem->list_ptr =
+			dsa_allocate0(LWLockTrancheNames.dsa, init_alloc * sizeof(dsa_pointer));
+
+		name_ptrs = dsa_get_address(LWLockTrancheNames.dsa, LWLockTrancheNames.shmem->list_ptr);
+		memset(name_ptrs, InvalidDsaPointer, init_alloc);
+#ifdef USE_INJECTION_POINTS
+		condition.initialized = true;
+		condition.allocated = init_alloc;
+#endif
+	}
+
+	/*
+	 * Need to enlarge the tranche array. Allocate a new list, copy the
+	 * current tranche name pointers into it, and update shared memory to
+	 * point to the new list.
+	 */
+	else if (tranche_index >= current_allocated)
+	{
+		int			new_alloc = pg_nextpower2_32(Max(LWLOCK_TRANCHE_NAMES_INIT_SIZE,
+													 tranche_index + 1));
+
+		dsa_pointer new_list = dsa_allocate(LWLockTrancheNames.dsa,
+											new_alloc * sizeof(dsa_pointer));
+
+		dsa_pointer *old_ptrs = dsa_get_address(LWLockTrancheNames.dsa,
+												LWLockTrancheNames.shmem->list_ptr);
+
+		name_ptrs = dsa_get_address(LWLockTrancheNames.dsa, new_list);
+
+		memset(name_ptrs, InvalidDsaPointer, new_alloc);
+		memcpy(name_ptrs, old_ptrs, sizeof(dsa_pointer) * current_allocated);
+
+		dsa_free(LWLockTrancheNames.dsa, LWLockTrancheNames.shmem->list_ptr);
+
+		LWLockTrancheNames.shmem->list_ptr = new_list;
+		LWLockTrancheNames.shmem->allocated = new_alloc;
+#ifdef USE_INJECTION_POINTS
+		condition.resized = true;
+		condition.allocated = new_alloc;
+#endif
+	}
+	/* Use the current list */
+	else
+	{
+		name_ptrs = dsa_get_address(LWLockTrancheNames.dsa,
+									LWLockTrancheNames.shmem->list_ptr);
+	}
 
-		newalloc = pg_nextpower2_32(Max(8, tranche_id + 1));
+	/* Allocate and copy new string */
+	len = strlen(tranche_name) + 1;
+	str_ptr = dsa_allocate(LWLockTrancheNames.dsa, len);
+	str_addr = dsa_get_address(LWLockTrancheNames.dsa, str_ptr);
+	memcpy(str_addr, tranche_name, len);
 
-		if (LWLockTrancheNames == NULL)
-			LWLockTrancheNames = (const char **)
+	name_ptrs[tranche_index] = str_ptr;
+
+	LWLockTrancheNames.shmem->max_tranche_index = tranche_index;
+
+	LWLockRelease(&LWLockTrancheNames.shmem->lock);
+#ifdef USE_INJECTION_POINTS
+	if (condition.resized || condition.initialized)
+		INJECTION_POINT("lwlock-sync-tranche-names", &condition);
+#endif
+}
+
+static void
+SetLocalTrancheName(int tranche_index, const char *tranche_name)
+{
+	int			newalloc;
+
+	/* If necessary, create or enlarge array. */
+	if (tranche_index >= LWLockTrancheNames.allocated)
+	{
+		newalloc = pg_nextpower2_32(Max(8, tranche_index + 1));
+
+		if (LWLockTrancheNames.local == NULL)
+		{
+			LWLockTrancheNames.local = (const char **)
 				MemoryContextAllocZero(TopMemoryContext,
 									   newalloc * sizeof(char *));
+		}
 		else
-			LWLockTrancheNames =
-				repalloc0_array(LWLockTrancheNames, const char *, LWLockTrancheNamesAllocated, newalloc);
-		LWLockTrancheNamesAllocated = newalloc;
+		{
+			LWLockTrancheNames.local =
+				repalloc0_array(LWLockTrancheNames.local, const char *, LWLockTrancheNames.allocated, newalloc);
+		}
+
+		LWLockTrancheNames.allocated = newalloc;
 	}
 
-	LWLockTrancheNames[tranche_id] = tranche_name;
+	/*
+	 * Don't override an existing name. Local cache may be ahead of shared
+	 * memory if tranche names were added locally during postmaster startup.
+	 */
+	if (LWLockTrancheNames.local[tranche_index])
+		return;
+
+	LWLockTrancheNames.local[tranche_index] = tranche_name;
 }
 
 /*
@@ -708,28 +844,76 @@ LWLockReportWaitEnd(void)
 	pgstat_report_wait_end();
 }
 
+static void
+SyncLWLockTrancheNames()
+{
+	dsa_pointer *pointers;
+
+	LWLockAcquire(&LWLockTrancheNames.shmem->lock, LW_SHARED);
+
+	pointers = dsa_get_address(LWLockTrancheNames.dsa, LWLockTrancheNames.shmem->list_ptr);
+
+	for (int i = 0; i <= LWLockTrancheNames.shmem->max_tranche_index; i++)
+	{
+		const char *tranche_name = (char *) dsa_get_address(LWLockTrancheNames.dsa, pointers[i]);
+
+		if (tranche_name)
+			SetLocalTrancheName(i, tranche_name);
+	}
+
+	LWLockRelease(&LWLockTrancheNames.shmem->lock);
+}
+
 /*
  * Return the name of an LWLock tranche.
  */
 static const char *
 GetLWTrancheName(uint16 trancheId)
 {
+	const char *tranche_name = NULL;
+#ifdef USE_INJECTION_POINTS
+	LWLockTranchesInjectionPoint condition = {SYNC_LOCAL, false, NULL};
+#endif
+
 	/* Built-in tranche or individual LWLock? */
 	if (trancheId < LWTRANCHE_FIRST_USER_DEFINED)
 		return BuiltinTrancheNames[trancheId];
 
+	trancheId -= LWTRANCHE_FIRST_USER_DEFINED;
+
 	/*
-	 * 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".
+	 * This is an extension tranche, so check LWLockTrancheNames[]. First,
+	 * synchronize the local list with shared memory if necessary, then
+	 * perform the lookup. If the tranche hasn't been registered by the
+	 * extension, return "extension".
 	 */
-	trancheId -= LWTRANCHE_FIRST_USER_DEFINED;
+	if (trancheId >= LWLockTrancheNames.allocated ||
+		LWLockTrancheNames.local[trancheId] == NULL)
+	{
+		/*
+		 * If it's not possible for the tranche to be in shared memory, skip
+		 * synchronization.
+		 */
+		if (trancheId <= LWLockTrancheNames.shmem->max_tranche_index)
+		{
+#ifdef USE_INJECTION_POINTS
+			condition.synced_local = true;
+#endif
+			SyncLWLockTrancheNames();
+		}
+	}
 
-	if (trancheId >= LWLockTrancheNamesAllocated ||
-		LWLockTrancheNames[trancheId] == NULL)
-		return "extension";
+	if (trancheId < LWLockTrancheNames.allocated)
+		tranche_name = LWLockTrancheNames.local[trancheId];
 
-	return LWLockTrancheNames[trancheId];
+	tranche_name = tranche_name ? tranche_name : "extension";
+#ifdef USE_INJECTION_POINTS
+	condition.tranche_name = tranche_name;
+	condition.tranche_index = trancheId;
+	condition.tranche_id = trancheId + LWTRANCHE_FIRST_USER_DEFINED;
+	INJECTION_POINT("lwlock-sync-tranche-names", &condition);
+#endif
+	return tranche_name;
 }
 
 /*
@@ -1995,3 +2179,121 @@ LWLockHeldByMeInMode(LWLock *lock, LWLockMode mode)
 	}
 	return false;
 }
+
+static Size
+LWLockTrancheNamesInitSize()
+{
+	Size		sz;
+
+	sz = 256 * 1024;
+
+	Assert(dsa_minimum_size() <= sz);
+
+	return MAXALIGN(sz);
+}
+
+Size
+LWLockTrancheNamesShmemSize(void)
+{
+	Size		sz;
+
+	sz = MAXALIGN(sizeof(LWLockTrancheNamesStruct));
+	sz = add_size(sz, LWLockTrancheNamesInitSize());
+
+	return sz;
+}
+
+void
+LWLockTrancheNamesShmemInit(void)
+{
+	bool		found;
+
+	/* Initialize or attach to shared memory structure */
+	LWLockTrancheNames.shmem = (LWLockTrancheNamesShmem *)
+		ShmemInitStruct("Dynamic LWLock tranche names",
+						LWLockTrancheNamesShmemSize(),
+						&found);
+
+	if (!IsUnderPostmaster)
+	{
+		dsa_area   *dsa;
+		LWLockTrancheNamesShmem *ctl = LWLockTrancheNames.shmem;
+		char	   *p = (char *) ctl;
+
+		/* Calculate layout within the shared memory region */
+		p += MAXALIGN(sizeof(LWLockTrancheNamesShmem));
+		ctl->raw_dsa_area = p;
+		p += MAXALIGN(LWLockTrancheNamesInitSize());
+
+		LWLockInitialize(&ctl->lock, LWTRANCHE_LWLOCK_TRANCHE_NAMES_LIST);
+
+		/* Create a dynamic shared memory area in-place */
+		dsa = dsa_create_in_place(ctl->raw_dsa_area,
+								  LWLockTrancheNamesInitSize(),
+								  LWTRANCHE_LWLOCK_TRANCHE_NAMES_DSA,
+								  NULL);
+
+		/*
+		 * Postmaster will never access these again, thus free the local
+		 * dsa/dshash references.
+		 */
+		dsa_pin(dsa);
+		dsa_detach(dsa);
+
+		ctl->allocated = 0;
+		ctl->max_tranche_index = 0;
+		ctl->list_ptr = InvalidDsaPointer;
+	}
+	else
+	{
+		Assert(found);
+	}
+}
+
+static void
+LWLockTrancheNamesAttach(void)
+{
+	MemoryContext oldcontext;
+
+	Assert(LWLockTrancheNames.dsa == NULL);
+
+	/* stats shared memory persists for the backend lifetime */
+	oldcontext = MemoryContextSwitchTo(TopMemoryContext);
+
+	LWLockTrancheNames.dsa = dsa_attach_in_place(LWLockTrancheNames.shmem->raw_dsa_area,
+												 NULL);
+	dsa_pin_mapping(LWLockTrancheNames.dsa);
+
+	Assert(LWLockTrancheNames.dsa);
+
+	MemoryContextSwitchTo(oldcontext);
+}
+
+static void
+LWLockTrancheNamesDetach(void)
+{
+	Assert(LWLockTrancheNames.dsa);
+
+	dsa_detach(LWLockTrancheNames.dsa);
+
+	dsa_release_in_place(LWLockTrancheNames.shmem->raw_dsa_area);
+
+	LWLockTrancheNames.dsa = NULL;
+}
+
+static void
+LWLockTrancheNamesShutdownHook(int code, Datum arg)
+{
+	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
+
+	LWLockTrancheNamesDetach();
+}
+
+void
+LWLockTrancheNamesBEInit(void)
+{
+	LWLockTrancheNamesAttach();
+
+	/* Set up a process-exit hook to clean up */
+	before_shmem_exit(LWLockTrancheNamesShutdownHook, 0);
+}
diff --git a/src/backend/utils/activity/wait_event.c b/src/backend/utils/activity/wait_event.c
index d9b8f34a355..eba7d338c1f 100644
--- a/src/backend/utils/activity/wait_event.c
+++ b/src/backend/utils/activity/wait_event.c
@@ -39,9 +39,6 @@ static const char *pgstat_get_wait_io(WaitEventIO w);
 static uint32 local_my_wait_event_info;
 uint32	   *my_wait_event_info = &local_my_wait_event_info;
 
-#define WAIT_EVENT_CLASS_MASK	0xFF000000
-#define WAIT_EVENT_ID_MASK		0x0000FFFF
-
 /*
  * Hash tables for storing custom wait event ids and their names in
  * shared memory.
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 0be307d2ca0..f60ed927892 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -406,6 +406,8 @@ SubtransSLRU	"Waiting to access the sub-transaction SLRU cache."
 XactSLRU	"Waiting to access the transaction status SLRU cache."
 ParallelVacuumDSA	"Waiting for parallel vacuum dynamic shared memory allocation."
 AioUringCompletion	"Waiting for another process to complete IO via io_uring."
+LWLockDynamicTranchesDSA	"Waiting to access the LWLock tranche names DSA area."
+LWLockDynamicTranchesList	"Waiting to access the list of LWLock tranche names in DSA."
 
 # No "ABI_compatibility" region here as WaitEventLWLock has its own C code.
 
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 641e535a73c..1ef04c6b5ec 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -662,6 +662,8 @@ BaseInit(void)
 	 * drop ephemeral slots, which in turn triggers stats reporting.
 	 */
 	ReplicationSlotInitialize();
+
+	LWLockTrancheNamesBEInit();
 }
 
 
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 5e717765764..8cf0fa7256a 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -80,6 +80,30 @@ typedef struct NamedLWLockTranche
 	char	   *trancheName;
 } NamedLWLockTranche;
 
+#ifdef USE_INJECTION_POINTS
+typedef enum LWLockTranchesInjectionPointTests
+{
+	SYNC_LOCAL = 0,
+	SET_SHARED,
+}			LWLockTranchesInjectionPointTests;
+
+typedef struct LWLockTranchesInjectionPoint
+{
+	int			test_type;
+
+	/* for SYNC_LOCAL tests */
+	bool		synced_local;
+	const char *tranche_name;
+	int			tranche_index;
+	int			tranche_id;
+
+	/* for SET_SHARED tests */
+	int			allocated;
+	bool		initialized;
+	bool		resized;
+}			LWLockTranchesInjectionPoint;
+#endif
+
 extern PGDLLIMPORT NamedLWLockTranche *NamedLWLockTrancheArray;
 extern PGDLLIMPORT int NamedLWLockTrancheRequests;
 
@@ -156,22 +180,18 @@ extern void RequestNamedLWLockTranche(const char *tranche_name, int num_lwlocks)
 extern LWLockPadded *GetNamedLWLockTranche(const char *tranche_name);
 
 /*
- * There is another, more flexible method of obtaining lwlocks. First, call
- * LWLockNewTrancheId just once to obtain a tranche ID; this allocates from
- * a shared counter.  Next, each individual process using the tranche should
- * call LWLockRegisterTranche() to associate that tranche ID with a name.
- * Finally, LWLockInitialize should be called just once per lwlock, passing
- * the tranche ID as an argument.
- *
- * It may seem strange that each process using the tranche must register it
- * separately, but dynamic shared memory segments aren't guaranteed to be
- * mapped at the same address in all coordinating backends, so storing the
- * registration in the main shared memory segment wouldn't work for that case.
+ * An alternative, more flexible method of obtaining lwlocks is available.
+ * First, call LWLockNewTrancheId once, providing an associated tranche name.
+ * This function allocates a tranche ID from a shared counter. Then, for each
+ * lwlock, call LWLockInitialize once, passing the tranche ID as an argument.
  */
-extern int	LWLockNewTrancheId(void);
-extern void LWLockRegisterTranche(int tranche_id, const char *tranche_name);
+extern int	LWLockNewTrancheId(const char *tranche_name);
 extern void LWLockInitialize(LWLock *lock, int tranche_id);
 
+extern void LWLockTrancheNamesShmemInit(void);
+extern Size LWLockTrancheNamesShmemSize(void);
+extern void LWLockTrancheNamesBEInit(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 208d2e3a8ed..5978648c09d 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -135,3 +135,5 @@ PG_LWLOCKTRANCHE(SUBTRANS_SLRU, SubtransSLRU)
 PG_LWLOCKTRANCHE(XACT_SLRU, XactSLRU)
 PG_LWLOCKTRANCHE(PARALLEL_VACUUM_DSA, ParallelVacuumDSA)
 PG_LWLOCKTRANCHE(AIO_URING_COMPLETION, AioUringCompletion)
+PG_LWLOCKTRANCHE(LWLOCK_TRANCHE_NAMES_DSA, LWLockDynamicTranchesDSA)
+PG_LWLOCKTRANCHE(LWLOCK_TRANCHE_NAMES_LIST, LWLockDynamicTranchesList)
diff --git a/src/include/utils/wait_classes.h b/src/include/utils/wait_classes.h
index 51ee68397d5..6ca0504cee1 100644
--- a/src/include/utils/wait_classes.h
+++ b/src/include/utils/wait_classes.h
@@ -10,6 +10,8 @@
 #ifndef WAIT_CLASSES_H
 #define WAIT_CLASSES_H
 
+#define WAIT_EVENT_CLASS_MASK	0xFF000000
+#define WAIT_EVENT_ID_MASK		0x0000FFFF
 
 /* ----------
  * Wait Classes
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 903a8ac151a..119d4708357 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -44,7 +44,8 @@ SUBDIRS = \
 		  test_tidstore \
 		  unsafe_tests \
 		  worker_spi \
-		  xid_wraparound
+		  xid_wraparound \
+		  test_lwlock_tranches \
 
 
 ifeq ($(enable_injection_points),yes)
diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build
index 93be0f57289..f04910d13d7 100644
--- a/src/test/modules/meson.build
+++ b/src/test/modules/meson.build
@@ -45,3 +45,4 @@ subdir('typcache')
 subdir('unsafe_tests')
 subdir('worker_spi')
 subdir('xid_wraparound')
+subdir('test_lwlock_tranches')
diff --git a/src/test/modules/test_dsa/test_dsa.c b/src/test/modules/test_dsa/test_dsa.c
index cd24d0f4873..01d5c6fa67f 100644
--- a/src/test/modules/test_dsa/test_dsa.c
+++ b/src/test/modules/test_dsa/test_dsa.c
@@ -29,8 +29,7 @@ test_dsa_basic(PG_FUNCTION_ARGS)
 	dsa_pointer p[100];
 
 	/* XXX: this tranche is leaked */
-	tranche_id = LWLockNewTrancheId();
-	LWLockRegisterTranche(tranche_id, "test_dsa");
+	tranche_id = LWLockNewTrancheId("test_dsa");
 
 	a = dsa_create(tranche_id);
 	for (int i = 0; i < 100; i++)
@@ -70,8 +69,7 @@ test_dsa_resowners(PG_FUNCTION_ARGS)
 	ResourceOwner childowner;
 
 	/* XXX: this tranche is leaked */
-	tranche_id = LWLockNewTrancheId();
-	LWLockRegisterTranche(tranche_id, "test_dsa");
+	tranche_id = LWLockNewTrancheId("test_dsa");
 
 	/* Create DSA in parent resource owner */
 	a = dsa_create(tranche_id);
diff --git a/src/test/modules/test_dsm_registry/test_dsm_registry.c b/src/test/modules/test_dsm_registry/test_dsm_registry.c
index 141c8ed1b34..4cc2ccdac3f 100644
--- a/src/test/modules/test_dsm_registry/test_dsm_registry.c
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry.c
@@ -48,7 +48,7 @@ init_tdr_dsm(void *ptr)
 {
 	TestDSMRegistryStruct *dsm = (TestDSMRegistryStruct *) ptr;
 
-	LWLockInitialize(&dsm->lck, LWLockNewTrancheId());
+	LWLockInitialize(&dsm->lck, LWLockNewTrancheId("test_dsm_registry"));
 	dsm->val = 0;
 }
 
@@ -61,7 +61,6 @@ tdr_attach_shmem(void)
 								 sizeof(TestDSMRegistryStruct),
 								 init_tdr_dsm,
 								 &found);
-	LWLockRegisterTranche(tdr_dsm->lck.tranche, "test_dsm_registry");
 
 	if (tdr_dsa == NULL)
 		tdr_dsa = GetNamedDSA("test_dsm_registry_dsa", &found);
diff --git a/src/test/modules/test_lwlock_tranches/Makefile b/src/test/modules/test_lwlock_tranches/Makefile
new file mode 100644
index 00000000000..902867229c1
--- /dev/null
+++ b/src/test/modules/test_lwlock_tranches/Makefile
@@ -0,0 +1,25 @@
+# src/test/modules/test_lwlock_tranches/Makefile
+
+MODULE_big = test_lwlock_tranches
+OBJS = \
+	$(WIN32RES) \
+	test_lwlock_tranches.o
+PGFILEDESC = "test_lwlock_tranches - test code LWLock tranche management"
+
+EXTENSION = test_lwlock_tranches
+DATA = test_lwlock_tranches--1.0.sql
+
+TAP_TESTS = 1
+
+export enable_injection_points
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_lwlock_tranches
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_lwlock_tranches/meson.build b/src/test/modules/test_lwlock_tranches/meson.build
new file mode 100644
index 00000000000..f32417a4143
--- /dev/null
+++ b/src/test/modules/test_lwlock_tranches/meson.build
@@ -0,0 +1,36 @@
+# Copyright (c) 2024-2025, PostgreSQL Global Development Group
+
+test_lwlock_tranches_sources = files(
+  'test_lwlock_tranches.c',
+)
+
+if host_system == 'windows'
+  test_lwlock_tranches_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_lwlock_tranches',
+    '--FILEDESC', 'test_lwlock_tranches - test code LWLock tranche management',])
+endif
+
+test_lwlock_tranches = shared_module('test_lwlock_tranches',
+  test_lwlock_tranches_sources,
+  kwargs: pg_test_mod_args,
+)
+test_install_libs += test_lwlock_tranches
+
+test_install_data += files(
+  'test_lwlock_tranches.control',
+  'test_lwlock_tranches--1.0.sql',
+)
+
+tests += {
+  'name': 'test_lwlock_tranches',
+  'sd': meson.current_source_dir(),
+  'bd': meson.current_build_dir(),
+  'tap': {
+    'env': {
+       'enable_injection_points': get_option('injection_points') ? 'yes' : 'no',
+    },
+    'tests': [
+      't/001_test_lwlock_tranches.pl',
+    ],
+  },
+}
\ No newline at end of file
diff --git a/src/test/modules/test_lwlock_tranches/t/001_test_lwlock_tranches.pl b/src/test/modules/test_lwlock_tranches/t/001_test_lwlock_tranches.pl
new file mode 100644
index 00000000000..1d39cbdf1ac
--- /dev/null
+++ b/src/test/modules/test_lwlock_tranches/t/001_test_lwlock_tranches.pl
@@ -0,0 +1,115 @@
+use strict;
+use warnings FATAL => 'all';
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+my $initial_size = 16;
+my $final_allocation = 64;
+
+my $node = PostgreSQL::Test::Cluster->new('primary');
+$node->init();
+$node->append_conf(
+	'postgresql.conf', qq(
+shared_preload_libraries=test_lwlock_tranches
+));
+$node->append_conf(
+	'postgresql.conf', qq(
+test_lwlock_tranches.requested_named_tranches=2
+));
+$node->start();
+
+$node->safe_psql('postgres', "CREATE EXTENSION injection_points");
+$node->safe_psql('postgres', "CREATE EXTENSION test_lwlock_tranches");
+
+my $first_user_defined = $node->safe_psql(
+	'postgres',
+	"SELECT test_lwlock_tranches_get_first_user_defined()");
+
+my $requested_named_tranches = $node->safe_psql(
+	'postgres',
+	"SELECT setting FROM pg_settings WHERE name = \'test_lwlock_tranches.requested_named_tranches\'");
+
+my $next_index;
+my $lookup_tranche_id = 0;
+my $log_location = -s $node->logfile;
+my $current_size = $initial_size;
+
+while ($current_size < $final_allocation) {
+	$current_size *= 2;
+}
+
+#
+# Create tranches using LWLockNewTrancheId. Note that tranches created
+# with RequestNamedLWLockTranche occur during startup.
+#
+$node->safe_psql('postgres',
+	qq{select test_lwlock_new_tranche_id($current_size - $requested_named_tranches)});
+
+$log_location = $node->wait_for_log(qr/ LOG:  allocation_reason = resize allocation_size = $current_size/, $log_location);
+ok(1, "resize up to $current_size tranche names");
+
+# Tests: Lookup of tranches created with RequestNamedLWLockTranche
+for ($next_index = 0; $next_index < $requested_named_tranches; $next_index++) {
+	$lookup_tranche_id = $next_index + $first_user_defined;
+
+	$node->safe_psql('postgres',
+		qq{select test_get_lwlock_identifier($next_index)});
+	$log_location = $node->wait_for_log(qr/ LOG:  tranche_name = test_lock_$next_index tranche_id = $lookup_tranche_id tranche_index = $next_index synced_local = no/, $log_location);
+}
+ok(1, "requested_named_tranches looked up from local");
+
+#
+# Tests: Lookup for tranches created directly with LWLockNewTrancheId.
+#
+# Two scenarios are tested:
+#  1. Test lookup for tranches created directly with LWLockNewTrancheId.
+#     We lookup twice to ensure the 2nd lookup occurs from the local
+#     cache.
+#  2. Test lookup to make sure that tranches created during postmaster startup,
+#     and are already in local memory are not overwritten during synchronization
+#     of local memory.
+#
+
+$lookup_tranche_id = $next_index + $first_user_defined;
+$node->safe_psql('postgres',
+	qq{select test_get_lwlock_identifier($next_index);
+	   select test_get_lwlock_identifier($next_index);
+	   select test_get_lwlock_identifier($next_index);
+	   select test_get_lwlock_identifier(0)});
+$node->wait_for_log(qr/ LOG:  tranche_name = test_lock__$next_index tranche_id = $lookup_tranche_id tranche_index = $next_index synced_local = yes/, $log_location);
+$node->wait_for_log(qr/ LOG:  tranche_name = test_lock__$next_index tranche_id = $lookup_tranche_id tranche_index = $next_index synced_local = no/, $log_location);
+$node->wait_for_log(qr/ LOG:  tranche_name = test_lock__$next_index tranche_id = $lookup_tranche_id tranche_index = $next_index synced_local = no/, $log_location);
+$log_location = $node->wait_for_log(qr/ LOG:  tranche_name = test_lock_0 tranche_id = $first_user_defined tranche_index = 0 synced_local = no/, $log_location);
+ok(1, "lookup with synchronization is successful");
+
+#
+# Tests: Lookup behavior for tranches never registered
+#        (via RequestNamedLWLockTranche or LWLockNewTrancheId).
+#
+# Two scenarios are tested:
+#  1. Tranche index within shared memory bounds.
+#     - shared memory is never synced in this case.
+#
+#  2. Tranche index outside shared memory bounds.
+#     - First lookup skips syncing local cache from shared memory.
+#
+
+# --- Scenario 1: Index within bounds ---
+$next_index = $current_size - $requested_named_tranches;
+$lookup_tranche_id = $next_index + $first_user_defined;
+$node->safe_psql('postgres',
+		qq{select test_get_lwlock_identifier($next_index)});
+$node->wait_for_log(qr/ LOG:  tranche_name = extension tranche_id = $lookup_tranche_id tranche_index = $next_index synced_local = no/, $log_location);
+ok(1, "tranche_id is within allocated amount. Returns \"extension\"");
+
+# --- Scenario 2: Index outside bounds ---
+$next_index = $current_size;
+$lookup_tranche_id = $next_index + $first_user_defined;
+$node->safe_psql('postgres',
+	qq{select test_get_lwlock_identifier($next_index)});
+$log_location = $node->wait_for_log(qr/LOG:  tranche_name = extension tranche_id = $lookup_tranche_id tranche_index = $next_index synced_local = no/, $log_location);
+ok(1, "tranche_id is outside allocated amount. Returns \"extension\"");
+
+done_testing();
\ No newline at end of file
diff --git a/src/test/modules/test_lwlock_tranches/test_lwlock_tranches--1.0.sql b/src/test/modules/test_lwlock_tranches/test_lwlock_tranches--1.0.sql
new file mode 100644
index 00000000000..7fdf90e3e23
--- /dev/null
+++ b/src/test/modules/test_lwlock_tranches/test_lwlock_tranches--1.0.sql
@@ -0,0 +1,16 @@
+-- test_lwlock_tranches--1.0.sql
+
+CREATE FUNCTION test_lwlock_new_tranche_id(bigint)
+RETURNS void
+AS 'MODULE_PATHNAME', 'test_lwlock_new_tranche_id'
+LANGUAGE C STRICT;
+
+CREATE FUNCTION test_get_lwlock_identifier(int)
+RETURNS void
+AS 'MODULE_PATHNAME', 'test_get_lwlock_identifier'
+LANGUAGE C STRICT;
+
+CREATE FUNCTION test_lwlock_tranches_get_first_user_defined()
+RETURNS int
+AS 'MODULE_PATHNAME', 'test_lwlock_tranches_get_first_user_defined'
+LANGUAGE C STRICT;
diff --git a/src/test/modules/test_lwlock_tranches/test_lwlock_tranches.c b/src/test/modules/test_lwlock_tranches/test_lwlock_tranches.c
new file mode 100644
index 00000000000..bd900be856c
--- /dev/null
+++ b/src/test/modules/test_lwlock_tranches/test_lwlock_tranches.c
@@ -0,0 +1,144 @@
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "miscadmin.h"
+#include "storage/dsm_registry.h"
+#include "storage/ipc.h"
+#include "storage/lwlock.h"
+#include "utils/guc.h"
+#include "utils/injection_point.h"
+#include "utils/wait_classes.h"
+
+PG_MODULE_MAGIC;
+
+#ifdef USE_INJECTION_POINTS
+extern PGDLLEXPORT void lwlock_tranches_injection_callback(const char *name,
+														   const void *private_data,
+														   void *arg);
+#else
+elog(ERROR, "injection points not supported");
+#endif
+
+/* hooks */
+static shmem_request_hook_type prev_shmem_request_hook = NULL;
+static shmem_startup_hook_type prev_shmem_startup_hook = NULL;
+static void test_lwlock_tranches_shmem_request(void);
+static void test_lwlock_tranches_shmem_startup(void);
+
+/* GUC */
+static int	test_lwlock_tranches_requested_named_tranches = 0;
+
+/*
+ * Module load callback
+ */
+void
+_PG_init(void)
+{
+	prev_shmem_request_hook = shmem_request_hook;
+	shmem_request_hook = test_lwlock_tranches_shmem_request;
+	prev_shmem_startup_hook = shmem_startup_hook;
+	shmem_startup_hook = test_lwlock_tranches_shmem_startup;
+
+	DefineCustomIntVariable("test_lwlock_tranches.requested_named_tranches",
+							"Sets the number of locks created during shmem request",
+							NULL,
+							&test_lwlock_tranches_requested_named_tranches,
+							2,
+							1,
+							UINT16_MAX,
+							PGC_POSTMASTER,
+							0,
+							NULL,
+							NULL,
+							NULL);
+}
+
+static void
+test_lwlock_tranches_shmem_startup(void)
+{
+	if (prev_shmem_startup_hook)
+		prev_shmem_startup_hook();
+
+#ifdef USE_INJECTION_POINTS
+	InjectionPointAttach("lwlock-sync-tranche-names",
+						 "test_lwlock_tranches",
+						 "lwlock_tranches_injection_callback",
+						 NULL,
+						 0);
+#else
+	elog(ERROR, "injection points not supported");
+#endif
+}
+
+static void
+test_lwlock_tranches_shmem_request(void)
+{
+	if (prev_shmem_request_hook)
+		prev_shmem_request_hook();
+
+	for (int i = 0; i < test_lwlock_tranches_requested_named_tranches; i++)
+	{
+		char		name[15];
+
+		snprintf(name, sizeof(name), "test_lock_%d", i);
+		RequestNamedLWLockTranche(name, i);
+	}
+}
+
+PG_FUNCTION_INFO_V1(test_lwlock_new_tranche_id);
+Datum
+test_lwlock_new_tranche_id(PG_FUNCTION_ARGS)
+{
+	int64		num = PG_GETARG_INT64(0);
+
+	for (int i = test_lwlock_tranches_requested_named_tranches; i < num; i++)
+	{
+		char		name[50];
+
+		snprintf(name, 50, "test_lock__%d", i);
+
+		LWLockNewTrancheId(name);
+	}
+
+	PG_RETURN_VOID();
+}
+
+#ifdef USE_INJECTION_POINTS
+void
+lwlock_tranches_injection_callback(const char *name, const void *private_data, void *arg)
+{
+	LWLockTranchesInjectionPoint *condition = (LWLockTranchesInjectionPoint *) arg;
+
+	if (condition->test_type == SYNC_LOCAL)
+		elog(LOG, "tranche_name = %s tranche_id = %d tranche_index = %d synced_local = %s",
+			 condition->tranche_name,
+			 condition->tranche_id,
+			 condition->tranche_index,
+			 condition->synced_local ? "yes" : "no");
+	else if (condition->test_type == SET_SHARED)
+		elog(LOG, "allocation_reason = %s allocation_size = %d",
+			 condition->initialized ? "initial" : "resize",
+			 condition->allocated);
+}
+#else
+elog(ERROR, "injection points not supported");
+#endif
+
+PG_FUNCTION_INFO_V1(test_get_lwlock_identifier);
+Datum
+test_get_lwlock_identifier(PG_FUNCTION_ARGS)
+{
+	int			id = PG_GETARG_INT32(0);
+	uint16		eventid = id + LWTRANCHE_FIRST_USER_DEFINED;
+
+	GetLWLockIdentifier(PG_WAIT_LWLOCK & WAIT_EVENT_CLASS_MASK, eventid);
+
+	PG_RETURN_VOID();
+}
+
+PG_FUNCTION_INFO_V1(test_lwlock_tranches_get_first_user_defined);
+Datum
+test_lwlock_tranches_get_first_user_defined(PG_FUNCTION_ARGS)
+{
+	return LWTRANCHE_FIRST_USER_DEFINED;
+}
diff --git a/src/test/modules/test_lwlock_tranches/test_lwlock_tranches.control b/src/test/modules/test_lwlock_tranches/test_lwlock_tranches.control
new file mode 100644
index 00000000000..bf0ecf64376
--- /dev/null
+++ b/src/test/modules/test_lwlock_tranches/test_lwlock_tranches.control
@@ -0,0 +1,6 @@
+# test_lwlock_tranches.control
+
+comment = 'Test LWLock tranch names tracking'
+default_version = '1.0'
+relocatable = false
+module_pathname = '$libdir/test_lwlock_tranches'
\ No newline at end of file
diff --git a/src/test/modules/test_radixtree/test_radixtree.c b/src/test/modules/test_radixtree/test_radixtree.c
index 80ad0296164..787162c8793 100644
--- a/src/test/modules/test_radixtree/test_radixtree.c
+++ b/src/test/modules/test_radixtree/test_radixtree.c
@@ -124,10 +124,9 @@ test_empty(void)
 	rt_iter    *iter;
 	uint64		key;
 #ifdef TEST_SHARED_RT
-	int			tranche_id = LWLockNewTrancheId();
+	int			tranche_id = LWLockNewTrancheId("test_radix_tree");
 	dsa_area   *dsa;
 
-	LWLockRegisterTranche(tranche_id, "test_radix_tree");
 	dsa = dsa_create(tranche_id);
 	radixtree = rt_create(dsa, tranche_id);
 #else
@@ -167,10 +166,9 @@ test_basic(rt_node_class_test_elem *test_info, int shift, bool asc)
 	uint64	   *keys;
 	int			children = test_info->nkeys;
 #ifdef TEST_SHARED_RT
-	int			tranche_id = LWLockNewTrancheId();
+	int			tranche_id = LWLockNewTrancheId("test_radix_tree");
 	dsa_area   *dsa;
 
-	LWLockRegisterTranche(tranche_id, "test_radix_tree");
 	dsa = dsa_create(tranche_id);
 	radixtree = rt_create(dsa, tranche_id);
 #else
@@ -304,10 +302,9 @@ test_random(void)
 	int			num_keys = 100000;
 	uint64	   *keys;
 #ifdef TEST_SHARED_RT
-	int			tranche_id = LWLockNewTrancheId();
+	int			tranche_id = LWLockNewTrancheId("test_radix_tree");
 	dsa_area   *dsa;
 
-	LWLockRegisterTranche(tranche_id, "test_radix_tree");
 	dsa = dsa_create(tranche_id);
 	radixtree = rt_create(dsa, tranche_id);
 #else
diff --git a/src/test/modules/test_slru/test_slru.c b/src/test/modules/test_slru/test_slru.c
index 32750930e43..8c0367eeee4 100644
--- a/src/test/modules/test_slru/test_slru.c
+++ b/src/test/modules/test_slru/test_slru.c
@@ -232,11 +232,9 @@ test_slru_shmem_startup(void)
 	(void) MakePGDirectory(slru_dir_name);
 
 	/* initialize the SLRU facility */
-	test_tranche_id = LWLockNewTrancheId();
-	LWLockRegisterTranche(test_tranche_id, "test_slru_tranche");
+	test_tranche_id = LWLockNewTrancheId("test_slru_tranche");
 
-	test_buffer_tranche_id = LWLockNewTrancheId();
-	LWLockRegisterTranche(test_tranche_id, "test_buffer_tranche");
+	test_buffer_tranche_id = LWLockNewTrancheId("test_buffer_tranche");
 
 	TestSlruCtl->PagePrecedes = test_slru_page_precedes_logically;
 	SimpleLruInit(TestSlruCtl, "TestSLRU",
diff --git a/src/test/modules/test_tidstore/test_tidstore.c b/src/test/modules/test_tidstore/test_tidstore.c
index eb16e0fbfa6..0c8f43867e5 100644
--- a/src/test/modules/test_tidstore/test_tidstore.c
+++ b/src/test/modules/test_tidstore/test_tidstore.c
@@ -103,8 +103,7 @@ test_create(PG_FUNCTION_ARGS)
 	{
 		int			tranche_id;
 
-		tranche_id = LWLockNewTrancheId();
-		LWLockRegisterTranche(tranche_id, "test_tidstore");
+		tranche_id = LWLockNewTrancheId("test_tidstore");
 
 		tidstore = TidStoreCreateShared(tidstore_max_size, tranche_id);
 
-- 
2.39.5 (Apple Git-154)

