From 65c80071e74c36328873dfa9cf6ce8f537d83850 Mon Sep 17 00:00:00 2001 From: Sami Imseih Date: Fri, 22 Aug 2025 00:35:45 -0500 Subject: [PATCH v20 2/2] Tests for LWLock tranche registration improvements --- src/test/modules/Makefile | 3 +- src/test/modules/meson.build | 1 + src/test/modules/test_tranches/.gitignore | 4 + 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 + 9 files changed, 415 insertions(+), 1 deletion(-) create mode 100644 src/test/modules/test_tranches/.gitignore 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/.gitignore b/src/test/modules/test_tranches/.gitignore new file mode 100644 index 00000000000..5dcb3ff9723 --- /dev/null +++ b/src/test/modules/test_tranches/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ 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..477035bdb99 --- /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 +# +$node->safe_psql('postgres', "select test_tranches_new(5);"); +my ($result, $stdout, $stderr) = $node->psql('postgres', + qq{ + 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: tranche 100 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(255)}); +$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: maximum number of tranches already 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]; + +$node->safe_psql('postgres', "select test_tranches_new(3);"); +($result, $stdout, $stderr) = $node->psql('postgres', + qq{ + 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: tranche 100 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: tranche 100 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.39.5 (Apple Git-154)