From 6923334bec327bb9cc089d7962bebc73df0afef6 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Date: Mon, 6 Apr 2026 16:46:33 +0530
Subject: [PATCH v20260406 5/6] Add minimum_size specification for resizable
 shared memory structures

Optional minimum size specification for resizable shared memory structures
allows us to enforce that a resizable structure cannot be shrunk below a certain
size.  For resizable structures, the minimum size should be less than or equal
to the initial size specified in ShmemRequestStructOpts::size. If not specified,
the minimum size defaults to 0 for resizable structures. For fixed-size
structures, the minimum size and maximum size are set to the initial size
specified in ShmemRequestStructOpts::size.

This makes maximum size and minimum size reported in pg_shmem_allocations view
consistent for both fixed-size and resizable structures.

Author: Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
---
 doc/src/sgml/system-views.sgml                |  16 ++-
 doc/src/sgml/xfunc.sgml                       |   8 +-
 src/backend/storage/ipc/shmem.c               | 110 ++++++++++++++----
 src/include/catalog/pg_proc.dat               |   4 +-
 src/include/storage/shmem.h                   |   6 +
 .../modules/resizable_shmem/resizable_shmem.c |   3 +
 .../resizable_shmem/t/001_resizable_shmem.pl  |   2 +-
 .../test_shmem/t/001_late_shmem_alloc.pl      |   8 ++
 src/test/regress/expected/rules.out           |   3 +-
 9 files changed, 129 insertions(+), 31 deletions(-)

diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 9717f8434bb..9bbbfdb37c5 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -4250,13 +4250,25 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>minimum_size</structfield> <type>int8</type>
+      </para>
+      <para>
+       Minimum size in bytes that the resizable allocation can shrink to. Equals
+       <structfield>size</structfield>For fixed-size allocations, anonymous
+       allocations, and free memory.
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>maximum_size</structfield> <type>int8</type>
       </para>
       <para>
-       Maximum size in bytes that the resizable allocation can grow to. Zero for
-       fixed-size allocations, for anonymous allocations, and for free memory.
+       Maximum size in bytes that the resizable allocation can grow to. Equals
+       <structfield>size</structfield> For fixed-size allocations, anonymous
+       allocations, and free memory.
       </para></entry>
      </row>
 
diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 3d25139c334..a6c7b8b1b22 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -3753,7 +3753,9 @@ my_shmem_init(void *arg)
      <parameter>.maximum_size</parameter> along with
      <parameter>.size</parameter>. <parameter>.maximum_size</parameter> is
      maximum size upto which the structure can grow where as
-     <parameter>.size</parameter> is the initial size of the structure.  While
+     <parameter>.size</parameter> is the initial size of the structure.
+     Optionally, <parameter>.minimum_size</parameter> can be set to the minimum
+     size that the structure can shrink to.  While
      contiguous address space worth <parameter>maximum_size</parameter> is
      allocated to the structure, only memory worth <parameter>size</parameter>
      bytes is allocated initially. The <function>init_fn</function> should only
@@ -3767,8 +3769,8 @@ my_shmem_init(void *arg)
     <para>
     The structure can be resized using <function>ShmemResizeStruct</function> by
     passing it the structure's <structname>ShmemStructDesc</structname> and the
-    new size which can be anywhere between 0 to
-    <parameter>maximum_size</parameter>. If the new size is smaller than the
+    new size which can be anywhere between <parameter>minimum_size</parameter>
+    and <parameter>maximum_size</parameter>. If the new size is smaller than the
     current size of the structure, the memory between the new size and current
     size is freed while keeping the contents of the memory upto new size intact.
     If the new size is greater than the current size, memory is allocated upto
diff --git a/src/backend/storage/ipc/shmem.c b/src/backend/storage/ipc/shmem.c
index 4a3e8a8769e..115c543d36a 100644
--- a/src/backend/storage/ipc/shmem.c
+++ b/src/backend/storage/ipc/shmem.c
@@ -111,8 +111,11 @@
  * startup, but memory is allocated or freed as the structure grows or shrinks
  * respectively. ShmemRequestStructOpts::size should be set to the initial size
  * of the structure, which is the amount of memory allocated at the startup.
- * After startup, the structure can be resized by calling ShmemResizeStruct() by
- * passing it the ShmemStructDesc for the structure and the new size.
+ * Optionally, ShmemRequestStructOpts::minimum_size can be set to the minimum
+ * size that the structure can shrink to. After startup, the structure can be
+ * resized by calling ShmemResizeStruct() by passing it the ShmemStructDesc for
+ * the structure and the new size. ShmemResizeStruct() enforces that the new
+ * size is within [minimum_size, maximum_size].
  *
  * While resizable structures can be created after the startup, the memory
  * available for them is quite limited.
@@ -294,6 +297,8 @@ typedef struct
 	void	   *location;		/* location in shared mem */
 	Size		size;			/* # bytes requested for the structure */
 	Size		allocated_size; /* # bytes actually allocated */
+	Size		minimum_size;	/* the minimum size the structure can shrink
+								 * to */
 	Size		maximum_size;	/* the maximum size the structure can grow to */
 	Size		reserved_space; /* the total address space reserved */
 } ShmemIndexEnt;
@@ -383,6 +388,9 @@ ShmemRequestInternal(ShmemStructOpts *options, ShmemRequestKind kind)
 		if (options->size <= 0 && options->size != SHMEM_ATTACH_UNKNOWN_SIZE)
 			elog(ERROR, "invalid size %zd for shared memory request for \"%s\"",
 				 options->size, options->name);
+		if (options->minimum_size < 0 && options->minimum_size != SHMEM_ATTACH_UNKNOWN_SIZE)
+			elog(ERROR, "invalid minimum_size %zd for shared memory request for \"%s\"",
+				 options->minimum_size, options->name);
 		if (options->maximum_size < 0 && options->maximum_size != SHMEM_ATTACH_UNKNOWN_SIZE)
 			elog(ERROR, "invalid maximum_size %zd for shared memory request for \"%s\"",
 				 options->maximum_size, options->name);
@@ -394,6 +402,11 @@ ShmemRequestInternal(ShmemStructOpts *options, ShmemRequestKind kind)
 		if (options->size <= 0)
 			elog(ERROR, "invalid size %zd for shared memory request for \"%s\"",
 				 options->size, options->name);
+		if (options->minimum_size == SHMEM_ATTACH_UNKNOWN_SIZE)
+			elog(ERROR, "SHMEM_ATTACH_UNKNOWN_SIZE cannot be used during startup");
+		if (options->minimum_size < 0)
+			elog(ERROR, "invalid minimum_size %zd for shared memory request for \"%s\"",
+				 options->minimum_size, options->name);
 		if (options->maximum_size == SHMEM_ATTACH_UNKNOWN_SIZE)
 			elog(ERROR, "SHMEM_ATTACH_UNKNOWN_SIZE cannot be used during startup");
 		if (options->maximum_size < 0)
@@ -405,10 +418,20 @@ ShmemRequestInternal(ShmemStructOpts *options, ShmemRequestKind kind)
 		elog(ERROR, "invalid alignment %zu for shared memory request for \"%s\"",
 			 options->alignment, options->name);
 
+	if (options->minimum_size > 0 && options->size != SHMEM_ATTACH_UNKNOWN_SIZE &&
+		options->minimum_size > options->size)
+		elog(ERROR, "resizable shared memory structure \"%s\" should have minimum size (%zd) less than or equal to size (%zd)",
+			 options->name, options->minimum_size, options->size);
+
 	if (options->maximum_size > 0 && options->size > options->maximum_size)
 		elog(ERROR, "resizable shared memory structure \"%s\" should have maximum size (%zd) greater than size (%zd)",
 			 options->name, options->maximum_size, options->size);
 
+	if (options->minimum_size > 0 && options->maximum_size > 0 &&
+		options->minimum_size > options->maximum_size)
+		elog(ERROR, "resizable shared memory structure \"%s\" should have minimum size (%zd) less than or equal to maximum size (%zd)",
+			 options->name, options->minimum_size, options->maximum_size);
+
 	/* Check that we're in the right state */
 	if (shmem_request_state != SRS_REQUESTING)
 		elog(ERROR, "ShmemRequestStruct can only be called from a shmem_request callback");
@@ -614,12 +637,19 @@ InitShmemIndexEntry(ShmemRequest *request)
 	index_entry->allocated_size = allocated_size;
 	index_entry->location = structPtr;
 	index_entry->reserved_space = allocated_size;
-	index_entry->maximum_size = request->options->maximum_size;
 	if (request->options->maximum_size > 0)
 	{
+		index_entry->minimum_size = request->options->minimum_size;
+		index_entry->maximum_size = request->options->maximum_size;
+
 		/* Adjust allocated size of a resizable structure. */
 		index_entry->allocated_size = EstimateAllocatedSize(index_entry);
 	}
+	else
+	{
+		index_entry->minimum_size = request->options->size;
+		index_entry->maximum_size = request->options->size;
+	}
 
 	/* Initialize depending on the kind of shmem area it is */
 	switch (request->kind)
@@ -674,14 +704,38 @@ AttachShmemIndexEntry(ShmemRequest *request, bool missing_ok)
 						name, index_entry->size, request->options->size)));
 	}
 
-	if (index_entry->maximum_size != request->options->maximum_size &&
-		request->options->maximum_size != SHMEM_ATTACH_UNKNOWN_SIZE)
+	/*
+	 * For resizable structures, also check that minimum_size and maximum_size
+	 * match. For fixed-size structures, these are derived (set to size) in
+	 * the index entry and not meaningful in the request.
+	 */
+	if (request->options->maximum_size != 0)
 	{
-		ereport(ERROR,
-				(errmsg("shared memory struct \"%s\" was created with" \
-						" different maximum_size: existing %zu, requested %zu",
-						name, index_entry->maximum_size,
-						request->options->maximum_size)));
+		if (index_entry->minimum_size != request->options->minimum_size &&
+			request->options->minimum_size != SHMEM_ATTACH_UNKNOWN_SIZE)
+		{
+			ereport(ERROR,
+					(errmsg("shared memory struct \"%s\" was created with"
+							" different minimum_size: existing %zu, requested %zu",
+							name, index_entry->minimum_size,
+							request->options->minimum_size)));
+		}
+
+		if (index_entry->maximum_size != request->options->maximum_size &&
+			request->options->maximum_size != SHMEM_ATTACH_UNKNOWN_SIZE)
+		{
+			ereport(ERROR,
+					(errmsg("shared memory struct \"%s\" was created with"
+							" different maximum_size: existing %zu, requested %zu",
+							name, index_entry->maximum_size,
+							request->options->maximum_size)));
+		}
+	}
+	else
+	{
+		if (index_entry->minimum_size != index_entry->maximum_size)
+			elog(ERROR, "shared memory struct \"%s\" was created as resizable, but requested as fixed-size",
+				 name);
 	}
 
 	/*
@@ -740,10 +794,11 @@ EstimateAllocatedSize(ShmemIndexEnt *entry)
 /*
  * ShmemResizeStruct() --- resize a resizable shared memory structure.
  *
- * If the structure is being shrunk, the memory pages that are no longer needed
- * are freed. If the structure is being expanded, the memory pages that are
- * needed for the new size are allocated. See EstimateAllocatedSize() for
- * explanation of which pages are allocated for a resizable structure.
+ * The new size must be within [minimum_size, maximum_size].  If the structure
+ * is being shrunk, the memory pages that are no longer needed are freed. If
+ * the structure is being expanded, the memory pages that are needed for the
+ * new size are allocated. See EstimateAllocatedSize() for explanation of which
+ * pages are allocated for a resizable structure.
  */
 void
 ShmemResizeStruct(const char *name, Size new_size)
@@ -776,15 +831,22 @@ ShmemResizeStruct(const char *name, Size new_size)
 
 	Assert(result);
 
-	if (result->maximum_size <= 0)
+	if (result->minimum_size == result->maximum_size)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("shared memory struct \"%s\" is not resizable", name)));
 
+	if (new_size < result->minimum_size)
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_RESOURCES),
+				 errmsg("cannot shrink shared memory structure \"%s\" below minimum size"
+						" (requested %zu bytes, minimum %zu bytes)",
+						name, new_size, result->minimum_size)));
+
 	if (result->maximum_size < new_size)
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_RESOURCES),
-				 errmsg("not enough address space is reserved for resizing structure \"%s\"" \
+				 errmsg("not enough address space is reserved for resizing structure \"%s\""
 						"(required %zu bytes, reserved %zu bytes)",
 						name, new_size, result->maximum_size)));
 
@@ -925,7 +987,8 @@ InitShmemAllocator(PGShmemHeader *seghdr)
 		result->size = ShmemAllocator->index_size;
 		result->allocated_size = ShmemAllocator->index_size;
 #ifdef HAVE_RESIZABLE_SHMEM
-		result->maximum_size = 0;
+		result->minimum_size = result->size;
+		result->maximum_size = result->size;
 		result->reserved_space = result->allocated_size;
 #endif
 		result->location = ShmemAllocator->index;
@@ -1271,7 +1334,7 @@ mul_size(Size s1, Size s2)
 Datum
 pg_get_shmem_allocations(PG_FUNCTION_ARGS)
 {
-#define PG_GET_SHMEM_SIZES_COLS 6
+#define PG_GET_SHMEM_SIZES_COLS 7
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	HASH_SEQ_STATUS hstat;
 	ShmemIndexEnt *ent;
@@ -1293,8 +1356,9 @@ pg_get_shmem_allocations(PG_FUNCTION_ARGS)
 		values[1] = Int64GetDatum((char *) ent->location - (char *) ShmemSegHdr);
 		values[2] = Int64GetDatum(ent->size);
 		values[3] = Int64GetDatum(ent->allocated_size);
-		values[4] = Int64GetDatum(ent->maximum_size);
-		values[5] = Int64GetDatum(ent->reserved_space);
+		values[4] = Int64GetDatum(ent->minimum_size);
+		values[5] = Int64GetDatum(ent->maximum_size);
+		values[6] = Int64GetDatum(ent->reserved_space);
 
 		/*
 		 * Keep track of the total reserved space for named shmem areas, to be
@@ -1313,8 +1377,9 @@ pg_get_shmem_allocations(PG_FUNCTION_ARGS)
 	nulls[1] = true;
 	values[2] = Int64GetDatum(ShmemAllocator->free_offset - named_allocated);
 	values[3] = values[2];
-	values[4] = Int64GetDatum(0);
+	values[4] = values[2];
 	values[5] = values[2];
+	values[6] = values[2];
 	tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
 
 	/* output as-of-yet unused shared memory */
@@ -1323,8 +1388,9 @@ pg_get_shmem_allocations(PG_FUNCTION_ARGS)
 	nulls[1] = false;
 	values[2] = Int64GetDatum(ShmemSegHdr->totalsize - ShmemAllocator->free_offset);
 	values[3] = values[2];
-	values[4] = Int64GetDatum(0);
+	values[4] = values[2];
 	values[5] = values[2];
+	values[6] = values[2];
 	tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
 
 	LWLockRelease(ShmemIndexLock);
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 32945d73f36..5ff6c543305 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8702,8 +8702,8 @@
 { oid => '5052', descr => 'allocations from the main shared memory segment',
   proname => 'pg_get_shmem_allocations', prorows => '50', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,int8,int8,int8,int8}', proargmodes => '{o,o,o,o,o,o}',
-  proargnames => '{name,off,size,allocated_size,maximum_size,reserved_space}',
+  proallargtypes => '{text,int8,int8,int8,int8,int8,int8}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,off,size,allocated_size,minimum_size,maximum_size,reserved_space}',
   prosrc => 'pg_get_shmem_allocations',
   proacl => '{POSTGRES=X,pg_read_all_stats=X}' },
 
diff --git a/src/include/storage/shmem.h b/src/include/storage/shmem.h
index 8140a0255ae..0e6d5a63f28 100644
--- a/src/include/storage/shmem.h
+++ b/src/include/storage/shmem.h
@@ -57,6 +57,12 @@ typedef struct ShmemStructOpts
 	 */
 	size_t		alignment;
 
+	/*
+	 * Minimum size this structure can shrink to. Should be set to 0 for
+	 * fixed-size structures.
+	 */
+	ssize_t		minimum_size;
+
 	/*
 	 * Maximum size this structure can grow upto in future. The memory is not
 	 * allocated right away but the corresponding address space is reserved so
diff --git a/src/test/modules/resizable_shmem/resizable_shmem.c b/src/test/modules/resizable_shmem/resizable_shmem.c
index fb3dfd64b4b..d035d767a62 100644
--- a/src/test/modules/resizable_shmem/resizable_shmem.c
+++ b/src/test/modules/resizable_shmem/resizable_shmem.c
@@ -144,14 +144,17 @@ resizable_shmem_request(void *arg)
 #ifdef HAVE_RESIZABLE_SHMEM
 	Size		max_size = add_size(offsetof(TestResizableShmemStruct, data),
 									mul_size(test_max_entries, TEST_ENTRY_SIZE));
+	Size		min_size = offsetof(TestResizableShmemStruct, data);
 
 #else
 	Size		max_size = 0;
+	Size		min_size = 0;
 #endif
 
 	/* Register our resizable shared memory structure */
 	ShmemRequestStruct(.name = "resizable_shmem",
 					   .size = use_unknown_size ? SHMEM_ATTACH_UNKNOWN_SIZE : initial_size,
+					   .minimum_size = min_size,
 					   .maximum_size = max_size,
 					   .ptr = (void **) &resizable_shmem,
 		);
diff --git a/src/test/modules/resizable_shmem/t/001_resizable_shmem.pl b/src/test/modules/resizable_shmem/t/001_resizable_shmem.pl
index a172cd0fd19..6a00ae0a194 100644
--- a/src/test/modules/resizable_shmem/t/001_resizable_shmem.pl
+++ b/src/test/modules/resizable_shmem/t/001_resizable_shmem.pl
@@ -96,7 +96,7 @@ sub run_resizable_tests
 		$session2->quit;
 
 		# Verify no other structure is resizable
-		is($node->safe_psql('postgres', "SELECT count(*) FROM pg_shmem_allocations WHERE name <> 'resizable_shmem' AND maximum_size <> 0;"),
+		is($node->safe_psql('postgres', "SELECT count(*) FROM pg_shmem_allocations WHERE name <> 'resizable_shmem' AND maximum_size <> minimum_size;"),
 							'0', "$prefix: no other resizable structures");
 
 		# Resize to maximum
diff --git a/src/test/modules/test_shmem/t/001_late_shmem_alloc.pl b/src/test/modules/test_shmem/t/001_late_shmem_alloc.pl
index c89b140871f..472d4b121ae 100644
--- a/src/test/modules/test_shmem/t/001_late_shmem_alloc.pl
+++ b/src/test/modules/test_shmem/t/001_late_shmem_alloc.pl
@@ -67,6 +67,14 @@ if ($have_resizable_shmem)
    like($stderr, qr/is not resizable/, "shrink error message mentions not resizable");
 }
 
+###
+# Test that minimum_size and maximum_size equal size for a fixed-size structure
+# in pg_shmem_allocations.
+###
+is($node->safe_psql('postgres',
+   "SELECT minimum_size = size AND maximum_size = size FROM pg_shmem_allocations WHERE name = 'test_shmem area';"),
+   't', "fixed-size structure has minimum_size = maximum_size = size");
+
 $node->stop;
 
 done_testing();
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2bbbf48c96a..c42eb9c67a1 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1771,9 +1771,10 @@ pg_shmem_allocations| SELECT name,
     off,
     size,
     allocated_size,
+    minimum_size,
     maximum_size,
     reserved_space
-   FROM pg_get_shmem_allocations() pg_get_shmem_allocations(name, off, size, allocated_size, maximum_size, reserved_space);
+   FROM pg_get_shmem_allocations() pg_get_shmem_allocations(name, off, size, allocated_size, minimum_size, maximum_size, reserved_space);
 pg_shmem_allocations_numa| SELECT name,
     numa_node,
     size
-- 
2.34.1

