From 85f7a097685a62ef0955a2d00756b3626de735cc Mon Sep 17 00:00:00 2001
From: Palak Chaturvedi <palakchaturvedi2843@gmail.com>
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

