From 3f7ad775a9fc82d2f183a370c2e0cfaf761b1393 Mon Sep 17 00:00:00 2001 From: Sami Imseih Date: Fri, 22 Aug 2025 00:35:45 -0500 Subject: [PATCH v11 2/2] 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 | 115 ++++++++++++ .../test_tranches/test_tranches--1.0.sql | 21 +++ .../modules/test_tranches/test_tranches.c | 165 ++++++++++++++++++ .../test_tranches/test_tranches.control | 6 + 8 files changed, 366 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..9a56b25e204 --- /dev/null +++ b/src/test/modules/test_tranches/t/001_test_tranches.pl @@ -0,0 +1,115 @@ +use strict; +use warnings FATAL => 'all'; + +use List::Util qw(min); +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; + +my $UINT16_MAX = 65535; +my $MAX_DYNAMIC_TRANCHES = 1024; + +# Helper function: wait for one or more logs +sub maybe_wait_for_log { + my ($node, $logs, $log_loc) = @_; + + # Normalize single regex to array + $logs = [$logs] unless ref($logs) eq 'ARRAY'; + + foreach my $regex (@$logs) { + $log_loc = $node->wait_for_log($regex, $log_loc); + } + return $log_loc; +} + +my $node = PostgreSQL::Test::Cluster->new('primary'); +$node->init(); + +my $requested_named_tranches = 4; + +$node->append_conf('postgresql.conf', + qq(shared_preload_libraries='test_tranches')); +$node->append_conf('postgresql.conf', + qq(test_tranches.requested_named_tranches=$requested_named_tranches)); +$node->append_conf('postgresql.conf', + qq(log_min_messages=DEBUG3)); +$node->start(); + +$node->safe_psql('postgres', q(CREATE EXTENSION test_tranches)); + +my $next_index = 0; +my $lookup_tranche_id = 0; + +my $log_location = -s $node->logfile; + +# +# Lookup requested tranches +# +my $last_tranche_id = $node->safe_psql('postgres', + qq{select test_tranches_last('fake')}); + +my $first_tranche_id = $last_tranche_id - $requested_named_tranches; +my $second_tranche_id = $first_tranche_id + 1; + +my $requested_tranches = $node->safe_psql('postgres', + qq{select test_tranches_lookup($first_tranche_id); + select test_tranches_lookup($second_tranche_id);}); + +ok ("test_lock_0\ntest_lock_1" eq $requested_tranches, "looked up requested tranches"); + +# +# Create tranches +# +$node->safe_psql('postgres', qq{select test_tranches_new(100)}); +my $next_tranche_id = $second_tranche_id + 1; +$requested_tranches = $node->safe_psql('postgres', + qq{select test_tranches_lookup($next_tranche_id);}); +ok ("test_lock_2" eq $requested_tranches, "looked up first dynamic tranches"); + +# +# get LWLocks +# +$node->safe_psql('postgres', + qq{select test_tranches_get_named_lwlock('test_lock_3', 3);}); + +# +# restart node without requested tranches to test limits +# +$node->append_conf('postgresql.conf', + qq(test_tranches.requested_named_tranches=0)); +$node->restart; + +# long tranche names +my $good_tranche_name = 'A' x 63; +my $bad_tranche_name = 'B' x 64; +$node->safe_psql('postgres', qq{select test_tranches_last('$good_tranche_name');}); +$node->psql('postgres', qq{select test_tranches_last('$bad_tranche_name');}); +$log_location = maybe_wait_for_log($node, qr/ ERROR: tranche name too long/, $log_location); +ok(1, "check for error on tranche name too long"); + +# +# Check for errors with not registered tranches +# +my $beyond_limit_id = $MAX_DYNAMIC_TRANCHES + 100; +$node->psql('postgres', + qq{select test_tranches_lookup($beyond_limit_id)}); +$log_location = maybe_wait_for_log($node, qr/ ERROR: LWLock tranche is not registered/, $log_location); + +my $within_limit_id = $MAX_DYNAMIC_TRANCHES - 100; +$node->psql('postgres', + qq{select test_tranches_lookup($within_limit_id)}); +$log_location = maybe_wait_for_log($node, qr/ ERROR: LWLock tranche is not registered/, $log_location); + +ok(1, "check for error with not registered tranches"); + + +# Check for errors with too many tranches registered +my $dynamic_tranches_count = $MAX_DYNAMIC_TRANCHES; +$node->safe_psql('postgres', + qq{select test_tranches_new($dynamic_tranches_count)}); +$node->psql('postgres', + qq{select test_tranches_new(1)}); +$log_location = maybe_wait_for_log($node, qr/ ERROR: too many LWLock tranches registered/, $log_location); +ok(1, "check for error with too many dynamic tranches"); + +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..1de8972ab41 --- /dev/null +++ b/src/test/modules/test_tranches/test_tranches--1.0.sql @@ -0,0 +1,21 @@ +-- 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_last(text) +RETURNS int +AS 'MODULE_PATHNAME', 'test_tranches_last' +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; 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..a27455ce5d2 --- /dev/null +++ b/src/test/modules/test_tranches/test_tranches.c @@ -0,0 +1,165 @@ +#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_last); +Datum +test_tranches_last(PG_FUNCTION_ARGS) +{ + PG_RETURN_INT32(LWLockNewTrancheId(text_to_cstring(PG_GETARG_TEXT_PP(0)))); +} + +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)); + + PG_RETURN_TEXT_P(cstring_to_text(tranche_name)); +} + +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); +} 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.39.5 (Apple Git-154)