Greetings,

When an SLRU leaves its tranche IDs at 0, shmem_slru_init() registers
tranches for it at startup.  LWLockShmemInit() runs a moment later and
resets the tranche count, so those tranches are forgotten.  Nothing
looks wrong at first, because every backend inherits the postmaster's
count.  But the next tranche anyone registers is handed the SLRU's ID
and takes over its name.  From then on, the SLRU's locks show up under
that name in pg_stat_activity and the trace probes, and looking up the
SLRU's other tranche can fail with "tranche N is not registered".

Once such an SLRU exists, any preloaded library that registers a tranche
is enough to trigger it.  No builtin SLRU leaves its IDs at 0, so
nothing shipping hits this yet.  That is the only way an extension can
define an SLRU, though, so the first extension that does will hit it.
This came in with 2e0943a8597 and is in REL_19_STABLE.

The attached patch adds a test that registers one more tranche and
checks the names.  Without the lwlock.c change, it fails on Linux and
Windows with:

  ERROR:  tranche 102 is not registered

I ran into this while setting up a TraceLogging (ETW) backend for the
static probes on Windows, which will be a separate patch.  Under
EXEC_BACKEND, backends don't inherit the postmaster's count, so with
the LWLock probes on, the first acquisition of one of the SLRU's locks
fails.

The fix initializes the tranche table once, from whichever of
LWLockNewTrancheId() and LWLockShmemInit() gets there first, and never
resets it after that.  If you'd rather fix the ordering in shmem.c, I
can rework it that way.


-- 
Bryan Green
EDB: https://www.enterprisedb.com
From a4ee8ccbf8b7b4a2214084f52a7f980ac8d05f6a Mon Sep 17 00:00:00 2001
From: Bryan Green <[email protected]>
Date: Wed, 28 Oct 2026 16:35:42 -0500
Subject: [PATCH] Don't lose LWLock tranches allocated during shmem
 initialization

shmem_slru_init() can allocate LWLock tranches for an SLRU before
LWLockShmemInit() runs, and LWLockShmemInit() then resets the tranche
counter, so those tranches are lost.  The next tranche allocated gets
one of their IDs and replaces its name, and looking up the other one
fails with "tranche %d is not registered".  Only test_slru does this in
core, but leaving the IDs at zero is the only way an extension can
define an SLRU.

Initialize the tranche registry on first use instead, from whichever of
LWLockNewTrancheId() and LWLockShmemInit() runs first.  Add a test to
test_slru that fails without the fix.
---
 src/backend/storage/lmgr/lwlock.c             | 32 +++++++++++++++++--
 .../modules/test_slru/expected/test_slru.out  |  7 ++++
 src/test/modules/test_slru/sql/test_slru.sql  |  3 ++
 src/test/modules/test_slru/test_slru--1.0.sql |  2 ++
 src/test/modules/test_slru/test_slru.c        | 18 +++++++++++
 5 files changed, 59 insertions(+), 3 deletions(-)

diff --git a/src/backend/storage/lmgr/lwlock.c 
b/src/backend/storage/lmgr/lwlock.c
index 82a1d4d2e26..423c867d8ca 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -197,6 +197,9 @@ static LWLockTrancheShmemData *LWLockTranches;
 /* backend-local copy of LWLockTranches->num_user_defined */
 static int     LocalNumUserDefinedTranches;
 
+/* is LWLockTranches initialized in this shmem segment?  Postmaster only */
+static bool LWLockTranchesInitialized = false;
+
 /*
  * NamedLWLockTrancheRequests is a list of tranches requested with
  * RequestNamedLWLockTranche().  It is only valid in the postmaster; after
@@ -215,6 +218,7 @@ static int  num_main_array_locks;
 
 static void LWLockShmemRequest(void *arg);
 static void LWLockShmemInit(void *arg);
+static void InitLWLockTranches(void);
 
 const ShmemCallbacks LWLockCallbacks = {
        .request_fn = LWLockShmemRequest,
@@ -422,6 +426,8 @@ LWLockShmemRequest(void *arg)
                                           .size = 
sizeof(LWLockTrancheShmemData),
                                           .ptr = (void **) &LWLockTranches,
                );
+       if (!IsUnderPostmaster)
+               LWLockTranchesInitialized = false;
 
        /* Space for the LWLock array */
        if (!IsUnderPostmaster)
@@ -447,9 +453,7 @@ LWLockShmemInit(void *arg)
        int                     pos;
 
        /* Initialize the dynamic-allocation counter for tranches */
-       LWLockTranches->num_user_defined = 0;
-
-       SpinLockInit(&LWLockTranches->lock);
+       InitLWLockTranches();
 
        /*
         * Allocate and initialize all LWLocks in the main array.  It includes 
all
@@ -499,6 +503,25 @@ LWLockShmemInit(void *arg)
        Assert(pos == num_main_array_locks);
 }
 
+/*
+ * shmem_slru_init() can allocate tranches before LWLockShmemInit() runs, so
+ * whichever comes first initializes the registry.
+ */
+static void
+InitLWLockTranches(void)
+{
+       Assert(!IsUnderPostmaster);
+
+       if (LWLockTranchesInitialized)
+               return;
+
+       LWLockTranches->num_user_defined = 0;
+       SpinLockInit(&LWLockTranches->lock);
+       LocalNumUserDefinedTranches = 0;
+
+       LWLockTranchesInitialized = true;
+}
+
 /*
  * InitLWLockAccess - initialize backend-local state needed to hold LWLocks
  */
@@ -575,6 +598,9 @@ LWLockNewTrancheId(const char *name)
                                 errdetail("LWLock tranche names must be no 
longer than %d bytes.",
                                                   NAMEDATALEN - 1)));
 
+       if (!IsUnderPostmaster)
+               InitLWLockTranches();
+
        /* The counter and the tranche names are protected by the spinlock */
        SpinLockAcquire(&LWLockTranches->lock);
 
diff --git a/src/test/modules/test_slru/expected/test_slru.out 
b/src/test/modules/test_slru/expected/test_slru.out
index 7ae1b0d4a98..09a6fdab4da 100644
--- a/src/test/modules/test_slru/expected/test_slru.out
+++ b/src/test/modules/test_slru/expected/test_slru.out
@@ -1,4 +1,11 @@
 CREATE EXTENSION test_slru;
+-- Tranches assigned by slru.c must survive startup
+SELECT test_slru_tranche_names();
+             test_slru_tranche_names             
+-------------------------------------------------
+ TestSLRU buffer, TestSLRU bank, test_slru extra
+(1 row)
+
 SELECT test_slru_page_exists(12345);
  test_slru_page_exists 
 -----------------------
diff --git a/src/test/modules/test_slru/sql/test_slru.sql 
b/src/test/modules/test_slru/sql/test_slru.sql
index c6454c5bf82..3b4b0919f3b 100644
--- a/src/test/modules/test_slru/sql/test_slru.sql
+++ b/src/test/modules/test_slru/sql/test_slru.sql
@@ -1,5 +1,8 @@
 CREATE EXTENSION test_slru;
 
+-- Tranches assigned by slru.c must survive startup
+SELECT test_slru_tranche_names();
+
 SELECT test_slru_page_exists(12345);
 SELECT test_slru_page_write(12345, 'Test SLRU');
 SELECT test_slru_page_read(12345);
diff --git a/src/test/modules/test_slru/test_slru--1.0.sql 
b/src/test/modules/test_slru/test_slru--1.0.sql
index 2148e7204ec..1500ad0be02 100644
--- a/src/test/modules/test_slru/test_slru--1.0.sql
+++ b/src/test/modules/test_slru/test_slru--1.0.sql
@@ -19,6 +19,8 @@ CREATE OR REPLACE FUNCTION test_slru_page_truncate(bigint) 
RETURNS VOID
   AS 'MODULE_PATHNAME', 'test_slru_page_truncate' LANGUAGE C;
 CREATE OR REPLACE FUNCTION test_slru_delete_all() RETURNS VOID
   AS 'MODULE_PATHNAME', 'test_slru_delete_all' LANGUAGE C;
+CREATE OR REPLACE FUNCTION test_slru_tranche_names() RETURNS text
+  AS 'MODULE_PATHNAME', 'test_slru_tranche_names' LANGUAGE C;
 
 
 CREATE OR REPLACE FUNCTION test_create_multixact() RETURNS xid
diff --git a/src/test/modules/test_slru/test_slru.c 
b/src/test/modules/test_slru/test_slru.c
index 40efffdbf62..45558b4e5c4 100644
--- a/src/test/modules/test_slru/test_slru.c
+++ b/src/test/modules/test_slru/test_slru.c
@@ -21,6 +21,7 @@
 #include "storage/ipc.h"
 #include "storage/shmem.h"
 #include "utils/builtins.h"
+#include "utils/wait_classes.h"
 
 PG_MODULE_MAGIC;
 
@@ -36,6 +37,7 @@ PG_FUNCTION_INFO_V1(test_slru_page_sync);
 PG_FUNCTION_INFO_V1(test_slru_page_delete);
 PG_FUNCTION_INFO_V1(test_slru_page_truncate);
 PG_FUNCTION_INFO_V1(test_slru_delete_all);
+PG_FUNCTION_INFO_V1(test_slru_tranche_names);
 
 /* Number of SLRU page slots */
 #define NUM_TEST_BUFFERS               16
@@ -198,6 +200,22 @@ test_slru_delete_all(PG_FUNCTION_ARGS)
        PG_RETURN_VOID();
 }
 
+Datum
+test_slru_tranche_names(PG_FUNCTION_ARGS)
+{
+       int                     buffer_tranche = 
TestSlruCtl->shared->buffer_locks[0].lock.tranche;
+       int                     bank_tranche = 
TestSlruCtl->shared->bank_locks[0].lock.tranche;
+       int                     new_tranche;
+
+       /* must not reuse the IDs slru.c assigned at startup */
+       new_tranche = LWLockNewTrancheId("test_slru extra");
+
+       PG_RETURN_TEXT_P(cstring_to_text(psprintf("%s, %s, %s",
+                                                                               
          GetLWLockIdentifier(PG_WAIT_LWLOCK, buffer_tranche),
+                                                                               
          GetLWLockIdentifier(PG_WAIT_LWLOCK, bank_tranche),
+                                                                               
          GetLWLockIdentifier(PG_WAIT_LWLOCK, new_tranche))));
+}
+
 static bool
 test_slru_page_precedes_logically(int64 page1, int64 page2)
 {
-- 
2.54.0.windows.1

Reply via email to