From 85f7a097685a62ef0955a2d00756b3626de735cc Mon Sep 17 00:00:00 2001 From: Palak Chaturvedi Date: Wed, 5 Aug 2026 14:53:38 +0000 Subject: [PATCH v20260813 2/2] test_shmem: make resizable shmem tests portable Fix several portability and synchronization problems in the resizable shared memory test. test_shmem_usage() followed every address-adjacent VMA, so any mapping that happened to abut the main shared-memory segment could be added to the shared-memory usage: a glibc arena on Linux 64-bit, the writable segments of loaded libraries on 32-bit, an AddressSanitizer shadow region, and so on. Continue across split VMAs only while they retain the target mapping's shared/private flag, inode, and pathname. Also emit a DEBUG1 log line for each VMA the walker includes, so future runs log which VMAs were summed. Use long for values read with the %ld conversion specifier. On any platform where long is not 64 bits (MSVC on x86_64, 32-bit Linux), sscanf writes only the low 32 bits into the int64 destination and leaves the upper 32 bits uninitialized. Beyond the current structure size the read loop was elided at -O2 because the CF-base assignments to a plain int32 sink were optimizable away. Accumulate loads through a volatile pointer with sink &= *(volatile int32 *) &data[i]; the volatile cast forces the compiler to issue each load, and the AND into a nonzero-initialized sink makes the return value depend on every element. After crashing a backend, wait for the post-reinitialization ready message in the server log. Polling SELECT 1 can succeed before the postmaster has processed SIGCHLD and reinitialized shared memory. Probe SHOW have_resizable_shmem before touching postgres -C. On builds without resizable shared memory support (Windows, or Linux without MADV_REMOVE / MADV_POPULATE_WRITE) skip the whole test. This avoids running postgres -C, which refuses when the Windows CI runner has an Administrator token, and also silently skips future platforms where resizable shared memory is unavailable. --- .../t/002_resizable_shmem_struct.pl | 20 +++++- src/test/modules/test_shmem/test_shmem.c | 72 +++++++++++-------- 2 files changed, 62 insertions(+), 30 deletions(-) diff --git a/src/test/modules/test_shmem/t/002_resizable_shmem_struct.pl b/src/test/modules/test_shmem/t/002_resizable_shmem_struct.pl index 4d2fd3c4282..a1f867e06e0 100644 --- a/src/test/modules/test_shmem/t/002_resizable_shmem_struct.pl +++ b/src/test/modules/test_shmem/t/002_resizable_shmem_struct.pl @@ -85,6 +85,10 @@ sub test_fault_beyond_size for my $mode ('write', 'read') { + # Wait for crash recovery, not just a connection accepted before the + # postmaster has processed SIGCHLD for the crashed backend. + my $log_offset = -s $node->logfile; + my ($ret, $stdout, $stderr) = $node->psql('postgres', "SELECT resizable_shmem_access_beyond_size('$mode');"); ok($ret != 0, "$prefix: $mode past current size crashes the backend"); @@ -92,8 +96,8 @@ sub test_fault_beyond_size qr/server closed the connection unexpectedly|connection to server was lost/, "$prefix: $mode crash reports lost connection"); - $node->poll_query_until('postgres', 'SELECT 1', '1') - or die "server did not come back after $mode crash"; + $node->wait_for_log(qr/database system is ready to accept connections/, + $log_offset); } is($node->safe_psql('postgres', @@ -278,6 +282,18 @@ $node->append_conf('postgresql.conf', 'max_locks_per_transaction = 10'); $node->append_conf('postgresql.conf', 'max_pred_locks_per_transaction = 10'); $node->append_conf('postgresql.conf', 'wal_buffers = 32kB'); +# Skip if resizable shared memory isn't supported on this build (e.g. Windows, +# or Linux without MADV_REMOVE / MADV_POPULATE_WRITE). +$node->start; +my $probe_have_resizable_shmem = + $node->safe_psql('postgres', 'SHOW have_resizable_shmem;'); +$node->stop; +if ($probe_have_resizable_shmem ne 'on') +{ + plan skip_all => + 'resizable shared memory is not supported on this build'; +} + ### # Test 1: Startup allocation via shared_preload_libraries ### diff --git a/src/test/modules/test_shmem/test_shmem.c b/src/test/modules/test_shmem/test_shmem.c index 83f8ea9cc3c..294c9663965 100644 --- a/src/test/modules/test_shmem/test_shmem.c +++ b/src/test/modules/test_shmem/test_shmem.c @@ -345,11 +345,9 @@ resizable_shmem_read(PG_FUNCTION_ARGS) * backend. * * The VMA containing our resizable_shmem pointer identifies the start of the - * main shared-memory segment. - * - * mprotect() calls issued when the resizable structure grows and shrinks can - * split the original mmap into several adjacent VMAs, so we sum the accounting - * fields across the base VMA and any VMAs contiguous with it. + * main shared-memory segment. mprotect() can split that mapping into adjacent + * VMAs, so include adjacent VMAs that share the same shared/private flag, + * inode, and pathname as the target VMA. */ PG_FUNCTION_INFO_V1(test_shmem_usage); Datum @@ -359,12 +357,15 @@ test_shmem_usage(PG_FUNCTION_ARGS) char line[256]; uintptr_t target = (uintptr_t) resizable_shmem; bool in_target_vma = false; + bool target_is_shared = false; + unsigned long target_inode = 0; + char target_path[256] = ""; bool use_hugetlb = (huge_pages_status == HUGE_PAGES_ON); unsigned long prev_end = 0; - int64 total_rss_kb = 0; - int64 total_swap_kb = 0; - int64 total_shared_hugetlb_kb = 0; - int64 val; + long total_rss_kb = 0; + long total_swap_kb = 0; + long total_shared_hugetlb_kb = 0; + long val; size_t result; f = AllocateFile("/proc/self/smaps", "r"); @@ -377,21 +378,36 @@ test_shmem_usage(PG_FUNCTION_ARGS) { unsigned long start; unsigned long end; - - if (sscanf(line, "%lx-%lx", &start, &end) == 2) + unsigned long offset; + unsigned long inode; + char perms[8]; + char dev[16]; + char path[256] = ""; + + if (sscanf(line, "%lx-%lx %7s %lx %15s %lu %*[ \t]%255[^\n]", + &start, &end, perms, &offset, dev, &inode, path) >= 6) { if (in_target_vma) { - /* - * Continue accumulating only across VMAs that are contiguous - * with the previous one; stop as soon as we hit a gap or a - * different mapping. - */ - if (start != prev_end) + if (start != prev_end + || (perms[3] == 's') != target_is_shared + || inode != target_inode + || strcmp(path, target_path) != 0) break; + + elog(DEBUG1, "test_shmem smaps includes contiguous VMA: %.*s", + (int) strcspn(line, "\n"), line); + } + else if (target >= start && target < end) + { + in_target_vma = true; + target_is_shared = (perms[3] == 's'); + target_inode = inode; + strlcpy(target_path, path, sizeof(target_path)); + + elog(DEBUG1, "test_shmem smaps includes target VMA: %.*s", + (int) strcspn(line, "\n"), line); } - else - in_target_vma = (target >= start && target < end); prev_end = end; } @@ -453,7 +469,7 @@ resizable_shmem_access_beyond_size(PG_FUNCTION_ARGS) text *mode_txt = PG_GETARG_TEXT_PP(0); const char *mode = text_to_cstring(mode_txt); bool do_write; - int32 sink = 0; + int32 sink = ~0; if (!resizable_shmem) ereport(ERROR, @@ -482,18 +498,18 @@ resizable_shmem_access_beyond_size(PG_FUNCTION_ARGS) ShmemProtectStruct("resizable_shmem"); #endif - for (int i = resizable_shmem->num_entries; i < test_max_entries; i++) + if (do_write) { - if (do_write) + for (int i = resizable_shmem->num_entries; i < test_max_entries; i++) resizable_shmem->data[i] = 0xdead; - else - sink = resizable_shmem->data[i]; + } + else + { + /* volatile cast prevents the compiler from eliding the loads. */ + for (int i = resizable_shmem->num_entries; i < test_max_entries; i++) + sink &= *(volatile int32 *) &resizable_shmem->data[i]; } - /* - * Return the last read value so that compiler doesn't optimize away the - * assignment to sink. - */ PG_RETURN_INT32(sink); } -- 2.43.0