From 04727b3f141de4923df6beebd2664321f132269f Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Tue, 12 Aug 2025 19:20:47 +0000 Subject: [PATCH v7 2/2] lwlock shared tranche names test --- src/backend/storage/lmgr/lwlock.c | 33 ++++- src/backend/utils/activity/wait_event.c | 3 - src/include/storage/lwlock.h | 24 +++ src/include/utils/wait_classes.h | 2 + src/test/modules/Makefile | 3 +- src/test/modules/meson.build | 1 + .../modules/test_lwlock_tranches/Makefile | 25 ++++ .../modules/test_lwlock_tranches/meson.build | 36 +++++ .../t/001_test_lwlock_tranches.pl | 117 +++++++++++++++ .../test_lwlock_tranches--1.0.sql | 16 ++ .../test_lwlock_tranches.c | 138 ++++++++++++++++++ .../test_lwlock_tranches.control | 6 + 12 files changed, 399 insertions(+), 5 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/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c index 7bf455beda4..aa99df36feb 100644 --- a/src/backend/storage/lmgr/lwlock.c +++ b/src/backend/storage/lmgr/lwlock.c @@ -85,6 +85,7 @@ #include "storage/proclist.h" #include "storage/procnumber.h" #include "storage/spin.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #ifdef LWLOCK_STATS @@ -652,6 +653,9 @@ SetSharedTrancheName(int tranche_index, const char *tranche_name) char *str_addr; int len; int current_allocated; +#ifdef USE_INJECTION_POINTS + LWLockTranchesInjectionPoint condition = {SET_SHARED, false, NULL, 0, false, false}; +#endif LWLockAcquire(&LWLockTrancheNames.shmem->lock, LW_EXCLUSIVE); @@ -668,6 +672,10 @@ SetSharedTrancheName(int tranche_index, const char *tranche_name) 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 } /* @@ -695,6 +703,10 @@ SetSharedTrancheName(int tranche_index, const char *tranche_name) 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 @@ -712,6 +724,10 @@ SetSharedTrancheName(int tranche_index, const char *tranche_name) name_ptrs[tranche_index] = str_ptr; LWLockRelease(&LWLockTrancheNames.shmem->lock); +#ifdef USE_INJECTION_POINTS + if (condition.resized || condition.initialized) + INJECTION_POINT("lwlock-sync-tranche-names", &condition); +#endif } /* @@ -900,6 +916,9 @@ 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) @@ -915,12 +934,24 @@ GetLWTrancheName(uint16 trancheId) */ if (trancheId >= LWLockTrancheNames.allocated || LWLockTrancheNames.local[trancheId] == NULL) + { +#ifdef USE_INJECTION_POINTS + condition.synced_local = true; +#endif SyncLWLockTrancheNames(trancheId); + } if (trancheId < LWLockTrancheNames.allocated) tranche_name = LWLockTrancheNames.local[trancheId]; - return tranche_name ? tranche_name : "extension"; + 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; } /* 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/include/storage/lwlock.h b/src/include/storage/lwlock.h index 8a008fc67dd..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; 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_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..843485bf939 --- /dev/null +++ b/src/test/modules/test_lwlock_tranches/t/001_test_lwlock_tranches.pl @@ -0,0 +1,117 @@ +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; + +SKIP: +{ + skip 'Injection points not supported by this build', 1 + unless $ENV{enable_injection_points} eq 'yes'; + + 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', q(CREATE EXTENSION injection_points)); + $node->safe_psql('postgres', q(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, based on + # test_lwlock_tranches.requested_named_tranches. + # + $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 shared memory with allocation 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 cache"); + + # + # Lookup for tranches created directly with LWLockNewTrancheId. + # + # Two scenarios are tested. First, the lookup for tranches created directly with + # LWLockNewTrancheId is performed twice to ensure the second lookup occurs from the + # local cache. Second, the lookup verifies that tranches created during postmaster + # startup, which are already in local memory, are not overwritten by 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); + select test_get_lwlock_identifier(1)}); + $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); + $lookup_tranche_id = $first_user_defined; + $node->wait_for_log(qr/ LOG: tranche_name = test_lock_0 tranche_id = $lookup_tranche_id tranche_index = 0 synced_local = no/, $log_location); + $lookup_tranche_id = $first_user_defined + 1; + $log_location = $node->wait_for_log(qr/ LOG: tranche_name = test_lock_1 tranche_id = $lookup_tranche_id tranche_index = 1 synced_local = no/, $log_location); + ok(1, "lookup with synchronization is successful"); + + # + # Lookup tranches that have not been registered with RequestNamedLWLockTranche + # or LWLockNewTrancheId. They should return "extension". We want to test when the + # tranche index is within the allocated slots in shared memory and outisde of the + # allocated slots. + # + $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); + 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 = yes/, $log_location); + $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, 'unnamed tranche_id is within allocated shared memory and returns "extension"'); + + $next_index = $current_size; + $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)}); + $node->wait_for_log(qr/LOG: tranche_name = extension tranche_id = $lookup_tranche_id tranche_index = $next_index synced_local = no/, $log_location); + $log_location = $node->wait_for_log(qr/LOG: tranche_name = extension tranche_id = $lookup_tranche_id tranche_index = $next_index synced_local = yes/, $log_location); + ok(1, 'unnamed tranche_id is outside of allocated shared memory and 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..4433984b833 --- /dev/null +++ b/src/test/modules/test_lwlock_tranches/test_lwlock_tranches.c @@ -0,0 +1,138 @@ +#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; + +extern PGDLLEXPORT void lwlock_tranches_injection_callback(const char *name, + const void *private_data, + void *arg); + +/* 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(); +} + +void +lwlock_tranches_injection_callback(const char *name, const void *private_data, void *arg) +{ +#ifdef USE_INJECTION_POINTS + 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); +#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 -- 2.43.0