From e4e81e8dfe45e78f27e2a9d6909383a09e5783a3 Mon Sep 17 00:00:00 2001
From: Sami Imseih <simseih@amazon.com>
Date: Fri, 22 Aug 2025 00:35:45 -0500
Subject: [PATCH v15 2/3] Tests for LWLock tranche registration improvements

---
 src/test/modules/Makefile                     |   3 +-
 src/test/modules/meson.build                  |   1 +
 src/test/modules/test_tranches/Makefile       |  23 +++
 src/test/modules/test_tranches/meson.build    |  33 +++
 .../test_tranches/t/001_test_tranches.pl      | 120 +++++++++++
 .../test_tranches/test_tranches--1.0.sql      |  35 ++++
 .../modules/test_tranches/test_tranches.c     | 191 ++++++++++++++++++
 .../test_tranches/test_tranches.control       |   6 +
 8 files changed, 411 insertions(+), 1 deletion(-)
 create mode 100644 src/test/modules/test_tranches/Makefile
 create mode 100644 src/test/modules/test_tranches/meson.build
 create mode 100644 src/test/modules/test_tranches/t/001_test_tranches.pl
 create mode 100644 src/test/modules/test_tranches/test_tranches--1.0.sql
 create mode 100644 src/test/modules/test_tranches/test_tranches.c
 create mode 100644 src/test/modules/test_tranches/test_tranches.control

diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 903a8ac151a..fd7aa4675c5 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_tranches
 
 
 ifeq ($(enable_injection_points),yes)
diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build
index 93be0f57289..52883dfb496 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_tranches')
diff --git a/src/test/modules/test_tranches/Makefile b/src/test/modules/test_tranches/Makefile
new file mode 100644
index 00000000000..5c9e78084da
--- /dev/null
+++ b/src/test/modules/test_tranches/Makefile
@@ -0,0 +1,23 @@
+# src/test/modules/test_tranches/Makefile
+
+MODULE_big = test_tranches
+OBJS = \
+	$(WIN32RES) \
+	test_tranches.o
+PGFILEDESC = "test_tranches - test code LWLock tranche management"
+
+EXTENSION = test_tranches
+DATA = test_tranches--1.0.sql
+
+TAP_TESTS = 1
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_tranches
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_tranches/meson.build b/src/test/modules/test_tranches/meson.build
new file mode 100644
index 00000000000..976ce9f0a25
--- /dev/null
+++ b/src/test/modules/test_tranches/meson.build
@@ -0,0 +1,33 @@
+# Copyright (c) 2024-2025, PostgreSQL Global Development Group
+
+test_tranches_sources = files(
+  'test_tranches.c',
+)
+
+if host_system == 'windows'
+  test_tranches_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_tranches',
+    '--FILEDESC', 'test_tranches - test code LWLock tranche management',])
+endif
+
+test_tranches = shared_module('test_tranches',
+  test_tranches_sources,
+  kwargs: pg_test_mod_args,
+)
+test_install_libs += test_tranches
+
+test_install_data += files(
+  'test_tranches.control',
+  'test_tranches--1.0.sql',
+)
+
+tests += {
+  'name': 'test_tranches',
+  'sd': meson.current_source_dir(),
+  'bd': meson.current_build_dir(),
+  'tap': {
+    'tests': [
+      't/001_test_tranches.pl',
+    ],
+  },
+}
\ No newline at end of file
diff --git a/src/test/modules/test_tranches/t/001_test_tranches.pl b/src/test/modules/test_tranches/t/001_test_tranches.pl
new file mode 100644
index 00000000000..dd500e49e24
--- /dev/null
+++ b/src/test/modules/test_tranches/t/001_test_tranches.pl
@@ -0,0 +1,120 @@
+use strict;
+use warnings FATAL => 'all';
+
+use List::Util qw(min);
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+#
+# Create the cluster without any requested tranches
+#
+my $node = PostgreSQL::Test::Cluster->new('primary');
+$node->init();
+$node->append_conf('postgresql.conf',
+    qq(shared_preload_libraries='test_tranches'));
+$node->append_conf('postgresql.conf',
+    qq(test_tranches.requested_named_tranches=0));
+$node->start();
+$node->safe_psql('postgres', q(CREATE EXTENSION test_tranches));
+
+my $first_user_defined = $node->safe_psql('postgres', q(select test_tranches_get_first_user_defined()));
+my @user_defined = ($first_user_defined .. $first_user_defined + 5);
+my ($second_user_defined,
+    $third_user_defined,
+    $fourth_user_defined,
+    $fifth_user_defined,
+    $sixth_user_defined) = @user_defined[1..5];
+#
+# Run lookup tests to ensure the correct tranche names are returned
+# and an error is raised for unregistered tranches
+#
+my ($result, $stdout, $stderr) = $node->psql('postgres',
+    qq{
+        select test_tranches_new(5);
+        select test_tranches_lookup(1);
+        select test_tranches_lookup($first_user_defined);
+        select test_tranches_lookup($second_user_defined);
+        select test_tranches_lookup($third_user_defined);
+        select test_tranches_lookup($fourth_user_defined);
+        select test_tranches_lookup($fifth_user_defined);
+        select test_tranches_lookup($sixth_user_defined);
+        },
+    on_error_stop => 0);
+like("$stderr", qr/ERROR:  LWLock tranche is not registered/, "unregistered tranche error");
+like("$stdout",
+     qr/ShmemIndex.*test_lock__0.*test_lock__1.*test_lock__2.*test_lock__3.*test_lock__4/s,
+     "match tranche names without requested tranches");
+
+#
+# Test the error for long tranche names
+#
+my $good_tranche_name = 'A' x 63;
+my $bad_tranche_name = 'B' x 64; # MAX_NAMED_TRANCHES_NAME_LEN
+$node->safe_psql('postgres', qq{select test_tranches_new_tranche('$good_tranche_name');});
+($result, $stdout, $stderr) = $node->psql('postgres',
+    qq{
+        select test_tranches_new_tranche('$bad_tranche_name');
+        },
+    on_error_stop => 0);
+like("$stderr", qr/ERROR:  tranche name too long/, "tranche name too long");
+
+$node->restart();
+
+#
+# Test the error when > MAX_NAMED_TRANCHES named tranches registered.
+#
+$node->safe_psql('postgres', qq{select test_tranches_new(127)});
+$node->safe_psql('postgres', qq{select test_tranches_new(1)});
+($result, $stdout, $stderr) = $node->psql('postgres', qq{select test_tranches_new(1);}, on_error_stop => 0);
+like("$stderr", qr/ERROR:  too many LWLock tranches registered/, "too many tranches registered");
+
+#
+# Repeat the lookup test with 2 requested tranches
+#
+$node->append_conf('postgresql.conf',
+    qq(test_tranches.requested_named_tranches=2));
+$node->restart();
+
+$first_user_defined = $first_user_defined;
+@user_defined = ($first_user_defined .. $first_user_defined + 5);
+($second_user_defined,
+    $third_user_defined,
+    $fourth_user_defined,
+    $fifth_user_defined,
+    $sixth_user_defined) = @user_defined[1..5];
+
+($result, $stdout, $stderr) = $node->psql('postgres',
+    qq{
+        select test_tranches_new(3);
+        select test_tranches_lookup(1);
+        select test_tranches_lookup($first_user_defined);
+        select test_tranches_lookup($second_user_defined);
+        select test_tranches_lookup($third_user_defined);
+        select test_tranches_lookup($fourth_user_defined);
+        select test_tranches_lookup($fifth_user_defined);
+        select test_tranches_lookup($sixth_user_defined);
+        },
+    on_error_stop => 0);
+like("$stderr", qr/ERROR:  LWLock tranche is not registered/, "unregistered tranche error");
+like("$stdout", qr/ShmemIndex.*test_lock_0.*test_lock_1.*test_lock__2.*test_lock__3.*test_lock__4/s,
+     "match tranche names with requested tranches");
+
+#
+# Test lwlock initialize
+#
+$node->safe_psql('postgres', qq{select test_tranches_lwlock_initialize($first_user_defined)});
+($result, $stdout, $stderr) = $node->psql('postgres',
+    qq{select test_tranches_lwlock_initialize($sixth_user_defined)},
+    on_error_stop => 0);
+like("$stderr", qr/ERROR:  LWLock tranche is not registered/, "LWLock intialization error on invalid tranche name");
+
+#
+# Test error for NULL tranche name
+#
+($result, $stdout, $stderr) = $node->psql('postgres',
+    qq{select test_tranches_new_tranche(NULL)},
+    on_error_stop => 0);
+like("$stderr", qr/ERROR:  tranche name cannot be null/, "NULL tranche name");
+
+done_testing();
diff --git a/src/test/modules/test_tranches/test_tranches--1.0.sql b/src/test/modules/test_tranches/test_tranches--1.0.sql
new file mode 100644
index 00000000000..f504098e04a
--- /dev/null
+++ b/src/test/modules/test_tranches/test_tranches--1.0.sql
@@ -0,0 +1,35 @@
+-- test_tranches--1.0.sql
+
+CREATE FUNCTION test_tranches_new(bigint)
+RETURNS void
+AS 'MODULE_PATHNAME', 'test_tranches_new'
+LANGUAGE C STRICT;
+
+CREATE FUNCTION test_tranches_lookup(int)
+RETURNS text
+AS 'MODULE_PATHNAME', 'test_tranches_lookup'
+LANGUAGE C STRICT;
+
+CREATE FUNCTION test_tranches_get_named_lwlock(text, int)
+RETURNS int
+AS 'MODULE_PATHNAME', 'test_tranches_get_named_lwlock'
+LANGUAGE C STRICT;
+
+CREATE FUNCTION test_tranches_get_first_user_defined()
+RETURNS int
+AS 'MODULE_PATHNAME', 'test_tranches_get_first_user_defined'
+LANGUAGE C STRICT;
+
+CREATE FUNCTION test_tranches_lwlock_initialize(int)
+RETURNS void
+AS 'MODULE_PATHNAME', 'test_tranches_lwlock_initialize'
+LANGUAGE C STRICT;
+
+/*
+ * Function is CALLED ON NULL INPUT to allow NULL input
+ * for testing
+ */
+CREATE FUNCTION test_tranches_new_tranche(text)
+RETURNS int
+AS 'MODULE_PATHNAME', 'test_tranches_new_tranche'
+LANGUAGE C;
\ No newline at end of file
diff --git a/src/test/modules/test_tranches/test_tranches.c b/src/test/modules/test_tranches/test_tranches.c
new file mode 100644
index 00000000000..7d3031ace9f
--- /dev/null
+++ b/src/test/modules/test_tranches/test_tranches.c
@@ -0,0 +1,191 @@
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "miscadmin.h"
+#include "storage/dsm_registry.h"
+#include "storage/ipc.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "utils/builtins.h"
+#include "utils/guc.h"
+#include "utils/injection_point.h"
+#include "utils/wait_classes.h"
+
+PG_MODULE_MAGIC;
+
+/* hooks */
+static shmem_request_hook_type prev_shmem_request_hook = NULL;
+static shmem_startup_hook_type prev_shmem_startup_hook = NULL;
+static void test_tranches_shmem_request(void);
+static void test_tranches_shmem_startup(void);
+
+/* GUC */
+static int	test_tranches_requested_named_tranches = 0;
+
+typedef struct testTranchesSharedState
+{
+	int			next_index;
+}			testTranchesSharedState;
+
+static testTranchesSharedState * test_lwlock_ss = NULL;
+
+/*
+ * LWLock wait event masks. Copied from src/backend/utils/activity/wait_event.c
+ */
+#define WAIT_EVENT_CLASS_MASK	0xFF000000
+
+/*
+ * Module load callback
+ */
+void
+_PG_init(void)
+{
+	prev_shmem_request_hook = shmem_request_hook;
+	shmem_request_hook = test_tranches_shmem_request;
+	prev_shmem_startup_hook = shmem_startup_hook;
+	shmem_startup_hook = test_tranches_shmem_startup;
+
+	DefineCustomIntVariable("test_tranches.requested_named_tranches",
+							"Sets the number of locks created during shmem request",
+							NULL,
+							&test_tranches_requested_named_tranches,
+							2,
+							0,
+							UINT16_MAX,
+							PGC_POSTMASTER,
+							0,
+							NULL,
+							NULL,
+							NULL);
+}
+
+static void
+test_tranches_shmem_startup(void)
+{
+	bool		found;
+
+	if (prev_shmem_startup_hook)
+		prev_shmem_startup_hook();
+
+	test_lwlock_ss = NULL;
+
+	test_lwlock_ss = ShmemInitStruct("test_tranches",
+									 sizeof(testTranchesSharedState),
+									 &found);
+	if (!found)
+	{
+		test_lwlock_ss->next_index = test_tranches_requested_named_tranches;
+	}
+}
+
+static Size
+test_tranches_memsize(void)
+{
+	Size		size;
+
+	size = MAXALIGN(sizeof(testTranchesSharedState));
+
+	return size;
+}
+
+static void
+test_tranches_shmem_request(void)
+{
+	int			i = 0;
+
+	if (prev_shmem_request_hook)
+		prev_shmem_request_hook();
+
+	RequestAddinShmemSpace(test_tranches_memsize());
+
+	for (i = 0; i < test_tranches_requested_named_tranches; i++)
+	{
+		char		name[15];
+
+		snprintf(name, sizeof(name), "test_lock_%d", i);
+		RequestNamedLWLockTranche(name, i);
+	}
+}
+
+PG_FUNCTION_INFO_V1(test_tranches_new);
+Datum
+test_tranches_new(PG_FUNCTION_ARGS)
+{
+	int64		num = PG_GETARG_INT64(0);
+	int			i;
+
+	for (i = test_lwlock_ss->next_index; i < num + test_lwlock_ss->next_index; i++)
+	{
+		char		name[50];
+
+		snprintf(name, 50, "test_lock__%d", i);
+
+		LWLockNewTrancheId(name);
+	}
+
+	test_lwlock_ss->next_index = i;
+
+	PG_RETURN_VOID();
+}
+
+PG_FUNCTION_INFO_V1(test_tranches_new_tranche);
+Datum
+test_tranches_new_tranche(PG_FUNCTION_ARGS)
+{
+	char	   *tranche_name = NULL;
+
+	if (!PG_ARGISNULL(0))
+		tranche_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
+
+	PG_RETURN_INT32(LWLockNewTrancheId(tranche_name));
+}
+
+PG_FUNCTION_INFO_V1(test_tranches_lookup);
+Datum
+test_tranches_lookup(PG_FUNCTION_ARGS)
+{
+	const char *tranche_name = GetLWLockIdentifier(PG_WAIT_LWLOCK & WAIT_EVENT_CLASS_MASK, PG_GETARG_INT32(0));
+
+	if (tranche_name)
+		PG_RETURN_TEXT_P(cstring_to_text(tranche_name));
+	else
+		PG_RETURN_NULL();
+}
+
+PG_FUNCTION_INFO_V1(test_tranches_get_named_lwlock);
+Datum
+test_tranches_get_named_lwlock(PG_FUNCTION_ARGS)
+{
+	int			i;
+	LWLockPadded *locks;
+
+	locks = GetNamedLWLockTranche(text_to_cstring(PG_GETARG_TEXT_PP(0)));
+
+	for (i = 0; i < PG_GETARG_INT32(1); i++)
+	{
+		LWLock	   *lock = &locks[i].lock;
+
+		LWLockAcquire(lock, LW_SHARED);
+		LWLockRelease(lock);
+	}
+
+	PG_RETURN_INT32(i);
+}
+
+PG_FUNCTION_INFO_V1(test_tranches_get_first_user_defined);
+Datum
+test_tranches_get_first_user_defined(PG_FUNCTION_ARGS)
+{
+	return LWTRANCHE_FIRST_USER_DEFINED;
+}
+
+PG_FUNCTION_INFO_V1(test_tranches_lwlock_initialize);
+Datum
+test_tranches_lwlock_initialize(PG_FUNCTION_ARGS)
+{
+	LWLock		lock;
+
+	LWLockInitialize(&lock, PG_GETARG_INT32(0));
+
+	PG_RETURN_VOID();
+}
diff --git a/src/test/modules/test_tranches/test_tranches.control b/src/test/modules/test_tranches/test_tranches.control
new file mode 100644
index 00000000000..8e6254a7e43
--- /dev/null
+++ b/src/test/modules/test_tranches/test_tranches.control
@@ -0,0 +1,6 @@
+# test_tranches.control
+
+comment = 'Test LWLock tranch names tracking'
+default_version = '1.0'
+relocatable = false
+module_pathname = '$libdir/test_tranches'
\ No newline at end of file
-- 
2.43.0

