From 09de808f14d11d26c45016ab9d81e911ef1e444c Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Date: Tue, 17 Feb 2026 16:51:20 +0530
Subject: [PATCH v20260405 15/15] resizable shared memory structures

Resizable shared memory structures can be allocated by specifying a new
member ShmemStructOpts::maximum_size. At the startup or when the
structure is created, we reserve address space worth maximum_size in the
shared memory segment. It is expected that the subsystem which creates
the structure would initialize only the initial size worth of memory
when creating it. In an mmap'ed memory, this should allocate memory
worth the initial size. It should not allocate maximum_size worth of
memory initially. As the structure is resized using ShmemResizeStruct()
memory is freed or allocated in chunks of memory pages when shrinking
and expanding the structure respectively.

Resizable shared memory feature depends upon existence of function
madvise() and constants MADV_REMOVE and MADV_WRITE_POPULATE.

On the platforms which do not have these, we disable this feature at
compile time. The commit introduces a compile time flag
HAVE_RESIZABLE_SHMEM which is defined if MADV_REMOVE and
MADV_WRITE_POPULATE exist. We don't check existence of madvise
separately, since existence of the constants implies existence of the
function.

HAVE_RESIZABLE_SHMEM is not defined in EXEC_BACKEND builds since that's
largely used for Windows where the APIs to free and allocate memory from
and to a given address space are not known to the author right now.
Given that PostgreSQL is used widely on Linux, providing this feature on
Linux covers benefits most of its users. Once we figure out the required
Windows APIs, we will support this feature on Windows as well.

The feature is also not available when Sys-V shared memory is used even
on Linux since we do not know whether required Sys-V APIs exist; mostly
they don't. Since that combination is only available for development and
testing, not supporting the feature there isn't going to impact
PostgreSQL users.

Using HAVE_RESIZABLE_SHMEM we disable compiling the code related to
resizable shared memory structures on the platforms which do not support
the feature. But we also have run time checks to disable this feature
when Sys-V shared memory is used. In order to know whether a given
instance of running server supports resizable structures, we have
introduced GUC have_resizable_shmem.

Author: Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
---
 configure.ac                                  |   4 +
 doc/src/sgml/config.sgml                      |  15 +
 doc/src/sgml/system-views.sgml                |  30 +-
 doc/src/sgml/xfunc.sgml                       |  54 +++
 meson.build                                   |  16 +
 src/backend/port/sysv_shmem.c                 |  69 ++++
 src/backend/port/win32_shmem.c                |  23 ++
 src/backend/storage/ipc/shmem.c               | 269 ++++++++++++--
 src/backend/utils/misc/guc_parameters.dat     |   7 +
 src/backend/utils/misc/guc_tables.c           |   7 +
 src/include/catalog/pg_proc.dat               |   4 +-
 src/include/pg_config.h.in                    |   8 +
 src/include/pg_config_manual.h                |   9 +
 src/include/storage/pg_shmem.h                |   5 +
 src/include/storage/shmem.h                   |  15 +
 src/test/modules/Makefile                     |   1 +
 src/test/modules/meson.build                  |   1 +
 src/test/modules/resizable_shmem/Makefile     |  25 ++
 src/test/modules/resizable_shmem/meson.build  |  36 ++
 .../resizable_shmem/resizable_shmem--1.0.sql  |  37 ++
 .../modules/resizable_shmem/resizable_shmem.c | 332 ++++++++++++++++++
 .../resizable_shmem/resizable_shmem.control   |   4 +
 .../resizable_shmem/t/001_resizable_shmem.pl  | 239 +++++++++++++
 .../test_shmem/t/001_late_shmem_alloc.pl      |  23 ++
 .../modules/test_shmem/test_shmem--1.0.sql    |   4 +
 src/test/modules/test_shmem/test_shmem.c      |  20 ++
 src/test/regress/expected/rules.out           |   6 +-
 src/tools/pgindent/typedefs.list              |   1 +
 28 files changed, 1228 insertions(+), 36 deletions(-)
 create mode 100644 src/test/modules/resizable_shmem/Makefile
 create mode 100644 src/test/modules/resizable_shmem/meson.build
 create mode 100644 src/test/modules/resizable_shmem/resizable_shmem--1.0.sql
 create mode 100644 src/test/modules/resizable_shmem/resizable_shmem.c
 create mode 100644 src/test/modules/resizable_shmem/resizable_shmem.control
 create mode 100644 src/test/modules/resizable_shmem/t/001_resizable_shmem.pl

diff --git a/configure.ac b/configure.ac
index ff5dd64468e..7acd844ccb2 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1895,6 +1895,10 @@ AC_CHECK_DECLS([memset_s], [], [], [#define __STDC_WANT_LIB_EXT1__ 1
 # This is probably only present on macOS, but may as well check always
 AC_CHECK_DECLS(F_FULLFSYNC, [], [], [#include <fcntl.h>])
 
+# Linux-specific madvise constants needed for resizable shared memory. See similar checks in meson.build for explanation of why these checks are here.
+AC_CHECK_DECLS([MADV_POPULATE_WRITE], [], [], [#include <sys/mman.h>])
+AC_CHECK_DECLS([MADV_REMOVE], [], [], [#include <sys/mman.h>])
+
 AC_REPLACE_FUNCS(m4_normalize([
 	explicit_bzero
 	getopt
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index d3fea738ca3..a42a173445e 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -12072,6 +12072,21 @@ dynamic_library_path = '/usr/local/lib/postgresql:$libdir'
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-have-resizable-shmem" xreflabel="have_resizable_shmem">
+      <term><varname>have_resizable_shmem</varname> (<type>boolean</type>)
+      <indexterm>
+       <primary><varname>have_resizable_shmem</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        Reports whether <productname>PostgreSQL</productname> has been built
+        with <literal>HAVE_RESIZABLE_SHMEM</literal> enabled and supports
+        <link linkend="xfunc-shared-addin-resizable">Resizable shared memory structures</link>.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="guc-huge-pages-status" xreflabel="huge_pages_status">
       <term><varname>huge_pages_status</varname> (<type>enum</type>)
       <indexterm>
diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 2ebec6928d5..9717f8434bb 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -4243,8 +4243,34 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
        Size of the allocation in bytes including padding. For anonymous
        allocations, no information about padding is available, so the
        <literal>size</literal> and <literal>allocated_size</literal> columns
-       will always be equal. Padding is not meaningful for free memory, so
-       the columns will be equal in that case also.
+       will always be equal. Padding is not meaningful for free memory, so the
+       columns will be equal in that case also. For resizable allocations which
+       may span multiple memory pages, the padding includes the padding due to
+       page alignment.
+      </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.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>reserved_space</structfield> <type>int8</type>
+      </para>
+      <para>
+       Address space reserved for the allocation in bytes. For resizable
+       structures, this is the total address space reserved to accommodate
+       growth up to <structfield>maximum_size</structfield>, and is greater
+       than or equal to <structfield>allocated_size</structfield>. For
+       fixed-size allocations, anonymous allocations, and free memory this
+       is same as <structfield>allocated_size</structfield>.
       </para></entry>
      </row>
     </tbody>
diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index aed3f2f0071..c2a0c5136af 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -3748,6 +3748,60 @@ my_shmem_init(void *arg)
      </para>
     </sect3>
 
+    <sect3 id="xfunc-shared-addin-resizable">
+     <title>Resizable shared memory structures</title>
+
+     <para>
+     A resizable memory structure can be requested using
+     <function>ShmemRequestStruct</function> by passing
+     <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
+     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
+     initialize the <parameter>size</parameter> amount of memory. The actual
+     memory allocated to this structure at any point in time is given by <link
+     linkend="view-pg-shmem-allocations"><structname>pg_shmem_allocations</structname>.<structfield>allocated_size</structfield></link>
+     and the address space reserved for this structure is given by <link
+     linkend="view-pg-shmem-allocations"><structname>pg_shmem_allocations</structname>.<structfield>reserved_space</structfield></link>.
+     </para>
+
+    <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
+    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
+    new size while keeping the current contents of the structure intact. The
+    starting address of the structure does not change because of resizing
+    operation. The caller may need to take care of the additional
+    synchronization between the resizing process and the processes using the
+    shared structure. Also accessing the memory beyond the current size of the
+    structure will not cause any segmentation fault or a bus error. Memory will
+    be allocated during such a write access. 0s will be returned on such a read
+    access if memory is not allocated yet. The additional synchronization may
+    use mprotect() with PROT_NONE in every backend that may access this memory
+    to ensure that such an access results in a fault.
+    </para>
+
+    <para>
+    This functionality is available only on the platforms which provide the APIs
+    necessary to reserve contiguous address space and to allocate or free memory
+    in that address space on demand. Macro <symbol>HAVE_RESIZABLE_SHMEM</symbol>
+    is defined on such platforms. It can be used to guard code related to
+    resizing a shared memory structure. The functionality is available on with
+    mmap'ed memory, so subsystems which use resizable structures may have to
+    addtionally disable resizable memory usage when <symbol>shared_memory_type</symbol> is not
+    <symbol>SHMEM_TYPE_MMAP</symbol>. A GUC <xref linkend="guc-have-resizable-shmem"/> is set to
+    <literal>on</literal> when this functionality is available in a running
+    server, <literal>off</literal> otherwise.
+    </para>
+    </sect3>
+
     <sect3 id="xfunc-shared-addin-dynamic">
      <title>Allocating Dynamic Shared Memory After Startup</title>
 
diff --git a/meson.build b/meson.build
index 43d5ffc30b1..790845762e1 100644
--- a/meson.build
+++ b/meson.build
@@ -2904,6 +2904,22 @@ decl_checks = [
   ['timingsafe_bcmp',  'string.h'],
 ]
 
+# Linux-specific madvise constants needed for resizable shared memory.
+# Usually we use AC_CHECK_DECLS to check for function declarations, but in this
+# case we are using it to detect existence of constants. These constants are
+# used to define HAVE_RESIZABLE_SHMEM which is used in storage/pg_shmem.h as
+# well as storage/shmem.h. The first abstracts the APIs to allocate shared
+# memory segments from the operating system whereas the second abstracts APIs to
+# allocate shared memory to various subsystems. Since they are related but
+# orthogonal to each other, including any one of them in the other file doesn't
+# make sense. pg_config_manual.h is the only place where HAVE_RESIZABLE_SHMEM
+# can be defined and made available to both without including sys/mman.h. But
+# for that we need constants that indicate the existence of following defines.
+decl_checks += [
+  ['MADV_POPULATE_WRITE', 'sys/mman.h'],
+  ['MADV_REMOVE', 'sys/mman.h'],
+]
+
 # Need to check for function declarations for these functions, because
 # checking for library symbols wouldn't handle deployment target
 # restrictions on macOS
diff --git a/src/backend/port/sysv_shmem.c b/src/backend/port/sysv_shmem.c
index 2e3886cf9fe..8d859dfbbfb 100644
--- a/src/backend/port/sysv_shmem.c
+++ b/src/backend/port/sysv_shmem.c
@@ -589,6 +589,27 @@ check_huge_page_size(int *newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * Get the page size being used by the shared memory.
+ *
+ * The function should be called only after the shared memory has been setup.
+ */
+Size
+GetOSPageSize(void)
+{
+	Size		os_page_size;
+
+	Assert(huge_pages_status != HUGE_PAGES_UNKNOWN);
+
+	os_page_size = sysconf(_SC_PAGESIZE);
+
+	/* If huge pages are actually in use, use huge page size */
+	if (huge_pages_status == HUGE_PAGES_ON)
+		GetHugePageSize(&os_page_size, NULL);
+
+	return os_page_size;
+}
+
 /*
  * Creates an anonymous mmap()ed shared memory segment.
  *
@@ -991,3 +1012,51 @@ PGSharedMemoryDetach(void)
 		AnonymousShmem = NULL;
 	}
 }
+
+#ifdef HAVE_RESIZABLE_SHMEM
+/*
+ * Make sure that the memory of given size from the given address is released.
+ *
+ * The address and size are expected to be page aligned.
+ *
+ * Only supported on platforms that support anonymous shared memory.
+ */
+void
+PGSharedMemoryEnsureFreed(void *addr, Size size)
+{
+	if (!AnonymousShmem)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("only anonymous shared memory can be freed")));
+
+	Assert(addr == (void *) TYPEALIGN(GetOSPageSize(), addr));
+	Assert(size == TYPEALIGN(GetOSPageSize(), size));
+
+	if (madvise(addr, size, MADV_REMOVE) == -1)
+		ereport(ERROR,
+				(errmsg("could not free shared memory: %m")));
+}
+
+/*
+ * Make sure that the memory of given size from the given address is allocated.
+ *
+ * The address and size are expected to be page aligned.
+ *
+ * Only supported on platforms that support anonymous shared memory.
+ */
+void
+PGSharedMemoryEnsureAllocated(void *addr, Size size)
+{
+	if (!AnonymousShmem)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("only anonymous shared memory can be allocated at runtime")));
+
+	Assert(addr == (void *) TYPEALIGN(GetOSPageSize(), addr));
+	Assert(size == TYPEALIGN(GetOSPageSize(), size));
+
+	if (madvise(addr, size, MADV_POPULATE_WRITE) == -1)
+		ereport(ERROR,
+				(errmsg("could not allocate shared memory: %m")));
+}
+#endif							/* HAVE_RESIZABLE_SHMEM */
diff --git a/src/backend/port/win32_shmem.c b/src/backend/port/win32_shmem.c
index 794e4fcb2ad..dc2ee018845 100644
--- a/src/backend/port/win32_shmem.c
+++ b/src/backend/port/win32_shmem.c
@@ -648,3 +648,26 @@ check_huge_page_size(int *newval, void **extra, GucSource source)
 	}
 	return true;
 }
+
+/*
+ * Get the page size used by the shared memory.
+ *
+ * The function should be called only after the shared memory has been setup.
+ */
+Size
+GetOSPageSize(void)
+{
+	SYSTEM_INFO sysinfo;
+	Size		os_page_size;
+
+	Assert(huge_pages_status != HUGE_PAGES_UNKNOWN);
+
+	GetSystemInfo(&sysinfo);
+	os_page_size = sysinfo.dwPageSize;
+
+	/* If huge pages are actually in use, use huge page size */
+	if (huge_pages_status == HUGE_PAGES_ON)
+		GetHugePageSize(&os_page_size, NULL);
+
+	return os_page_size;
+}
diff --git a/src/backend/storage/ipc/shmem.c b/src/backend/storage/ipc/shmem.c
index 973811e545e..4eae14bede7 100644
--- a/src/backend/storage/ipc/shmem.c
+++ b/src/backend/storage/ipc/shmem.c
@@ -19,11 +19,11 @@
  * methods).  The routines in this file are used for allocating and
  * binding to shared memory data structures.
  *
- * This module provides facilities to allocate fixed-size structures in shared
- * memory, for things like variables shared between all backend processes.
- * Each such structure has a string name to identify it, specified when it is
- * requested.  shmem_hash.c provides a shared hash table implementation on top
- * of that.
+ * This module provides facilities to allocate fixed-size as well as resizable
+ * structures in shared memory, for things like variables shared between all
+ * backend processes. Each such structure has a string name to identify it,
+ * specified when it is requested.  shmem_hash.c provides a shared hash table
+ * implementation on top of fixed-size structures.
  *
  * Shared memory areas should usually not be allocated after postmaster
  * startup, although we do allow small allocations later for the benefit of
@@ -106,6 +106,21 @@
  * (*ShmemStructDesc->ptr), and calls the attach_fn callback, if any, for
  * additional per-backend setup.
  *
+ * Resizable shared memory structures
+ * ----------------------------------
+ *
+ * In order to allocate resizable shared memory structures, set
+ * ShmemRequestStructOpts::maximum_size to the maximum size that the structure
+ * can grow to.  The address space for the maximum size will be reserved at
+ * 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.
+ *
+ * While resizable structures can be created after the startup, the memory
+ * available for them is quite limited.
+ *
  * Legacy ShmemInitStruct()/ShmemInitHash() functions
  * --------------------------------------------------
  *
@@ -170,6 +185,18 @@ typedef struct
 	ShmemRequestKind kind;
 } ShmemRequest;
 
+/*
+ * A convenient macro to get the space required for a shmem request consistently.
+ * A resizable structure, requested by non-zero maximum_size, requires space for
+ * its maximum size.
+ */
+#ifdef HAVE_RESIZABLE_SHMEM
+#define SHMEM_REQUEST_SPACE_SIZE(request) \
+	((request)->options->maximum_size > 0 ? (request)->options->maximum_size : (request)->options->size)
+#else
+#define SHMEM_REQUEST_SPACE_SIZE(request) ((request)->options->size)
+#endif
+
 static List *pending_shmem_requests;
 
 /*
@@ -272,6 +299,10 @@ typedef struct
 	void	   *location;		/* location in shared mem */
 	Size		size;			/* # bytes requested for the structure */
 	Size		allocated_size; /* # bytes actually allocated */
+#ifdef HAVE_RESIZABLE_SHMEM
+	Size		maximum_size;	/* the maximum size the structure can grow to */
+	Size		reserved_space; /* the total address space reserved */
+#endif
 } ShmemIndexEnt;
 
 /* To get reliable results for NUMA inquiry we need to "touch pages" once */
@@ -280,6 +311,9 @@ static bool firstNumaTouch = true;
 static void CallShmemCallbacksAfterStartup(const ShmemCallbacks *callbacks);
 static void InitShmemIndexEntry(ShmemRequest *request);
 static bool AttachShmemIndexEntry(ShmemRequest *request, bool missing_ok);
+#ifdef HAVE_RESIZABLE_SHMEM
+static Size EstimateAllocatedSize(ShmemIndexEnt *entry);
+#endif
 
 Datum		pg_numa_available(PG_FUNCTION_ARGS);
 
@@ -350,6 +384,11 @@ 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);
+#ifdef HAVE_RESIZABLE_SHMEM
+		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);
+#endif
 	}
 	else
 	{
@@ -358,8 +397,24 @@ ShmemRequestInternal(ShmemStructOpts *options, ShmemRequestKind kind)
 		if (options->size <= 0)
 			elog(ERROR, "invalid size %zd for shared memory request for \"%s\"",
 				 options->size, options->name);
+#ifdef HAVE_RESIZABLE_SHMEM
+		if (options->maximum_size == SHMEM_ATTACH_UNKNOWN_SIZE)
+			elog(ERROR, "SHMEM_ATTACH_UNKNOWN_SIZE cannot be used during startup");
+		if (options->maximum_size < 0)
+			elog(ERROR, "invalid maximum_size %zd for shared memory request for \"%s\"",
+				 options->maximum_size, options->name);
+#endif
 	}
 
+#ifdef HAVE_RESIZABLE_SHMEM
+	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->maximum_size > 0 && shared_memory_type != SHMEM_TYPE_MMAP)
+		elog(ERROR, "resizable shared memory requires shared_memory_type = mmap");
+#endif
+
 	if (shmem_request_state != SRS_REQUESTING)
 		elog(ERROR, "ShmemRequestStruct can only be called from a shmem_request callback");
 
@@ -379,8 +434,13 @@ ShmemRequestInternal(ShmemStructOpts *options, ShmemRequestKind kind)
 }
 
 /*
- *	ShmemGetRequestedSize() --- estimate the total size of all registered shared
- *                              memory structures.
+ * ShmemGetRequestedSize() --- estimate the total size of all registered shared
+ * memory structures.
+ *
+ * When maximum_size is specified when requesting resizable shared memory
+ * structures. We use that, instead of the (initial) size, for the estimation,
+ * to ensure that enough space is reserved for growing the resizable structures
+ * to its maximum size.
  *
  * This is called once at postmaster startup, before the shared memory segment
  * has been created.
@@ -398,7 +458,7 @@ ShmemGetRequestedSize(void)
 	/* memory needed for all the requested areas */
 	foreach_ptr(ShmemRequest, request, pending_shmem_requests)
 	{
-		size = add_size(size, request->options->size);
+		size = add_size(size, SHMEM_REQUEST_SPACE_SIZE(request));
 		/* calculate alignment padding like ShmemAllocRaw() does */
 		size = TYPEALIGN(Max(request->options->alignment, PG_CACHE_LINE_SIZE),
 						 size);
@@ -506,6 +566,7 @@ InitShmemIndexEntry(ShmemRequest *request)
 	ShmemIndexEnt *index_entry;
 	bool		found;
 	size_t		allocated_size;
+	size_t		requested_size;
 	void	   *structPtr;
 
 	/* look it up in the shmem index */
@@ -523,10 +584,18 @@ InitShmemIndexEntry(ShmemRequest *request)
 	}
 
 	/*
-	 * We inserted the entry to the shared memory index.  Allocate requested
-	 * amount of shared memory for it, and initialize the index entry.
+	 * We inserted the entry to the shared memory index. Allocate requested
+	 * amount of address space in the shared memory segment for it, and do
+	 * basic initializion. The memory gets allocated during initialization as
+	 * the corresponding memory pages are written to.  Allocate enough space
+	 * for a resizable structure to grow to its maximum size. It is expected
+	 * that the initialization callback will use only as much memory as the
+	 * initial size of the resizable structure. (Well, if it doesn't, more
+	 * memory will be allocated initially than expected, no further harm is
+	 * done.)
 	 */
-	structPtr = ShmemAllocRaw(request->options->size,
+	requested_size = SHMEM_REQUEST_SPACE_SIZE(request);
+	structPtr = ShmemAllocRaw(requested_size,
 							  request->options->alignment,
 							  &allocated_size);
 	if (structPtr == NULL)
@@ -535,13 +604,22 @@ InitShmemIndexEntry(ShmemRequest *request)
 		hash_search(ShmemIndex, name, HASH_REMOVE, NULL);
 		ereport(ERROR,
 				(errcode(ERRCODE_OUT_OF_MEMORY),
-				 errmsg("not enough shared memory for data structure"
+				 errmsg("not enough shared memory space for data structure"
 						" \"%s\" (%zu bytes requested)",
-						name, request->options->size)));
+						name, requested_size)));
 	}
 	index_entry->size = request->options->size;
 	index_entry->allocated_size = allocated_size;
 	index_entry->location = structPtr;
+#ifdef HAVE_RESIZABLE_SHMEM
+	index_entry->reserved_space = allocated_size;
+	index_entry->maximum_size = request->options->maximum_size;
+	if (request->options->maximum_size > 0)
+	{
+		/* Adjust allocated size of a resizable structure. */
+		index_entry->allocated_size = EstimateAllocatedSize(index_entry);
+	}
+#endif
 
 	/* Initialize depending on the kind of shmem area it is */
 	switch (request->kind)
@@ -586,7 +664,7 @@ AttachShmemIndexEntry(ShmemRequest *request, bool missing_ok)
 		return false;
 	}
 
-	/* Check that the size in the index matches the request. */
+	/* Check that the sizes in the index match the request. */
 	if (index_entry->size != request->options->size &&
 		request->options->size != SHMEM_ATTACH_UNKNOWN_SIZE)
 	{
@@ -596,6 +674,18 @@ AttachShmemIndexEntry(ShmemRequest *request, bool missing_ok)
 						name, index_entry->size, request->options->size)));
 	}
 
+#ifdef HAVE_RESIZABLE_SHMEM
+	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)));
+	}
+#endif
+
 	/*
 	 * Re-establish the caller's pointer variable, or do other actions to
 	 * attach depending on the kind of shmem area it is.
@@ -617,6 +707,115 @@ AttachShmemIndexEntry(ShmemRequest *request, bool missing_ok)
 	return true;
 }
 
+#ifdef HAVE_RESIZABLE_SHMEM
+/*
+ * Estimate the actual memory allocated for a resizable structure.
+ *
+ * ... based on the assumption that the memory is allocated in pages.
+ *
+ * The memory pages covered by the current size of a resizable structure are
+ * fully allocated when the currently allocated part of the structure is written
+ * to. The memory page where the maximal structure ends also hosts the next
+ * structure, unless the maximal structure ends on a page boundary. Hence that
+ * page is allocated when the next structure is written to. The memory pages
+ * between the page where the current structure ends and the page where the next
+ * structure starts remain unallocated. Thus the memory allocated for a
+ * resizable structure can be estimated as the total address space reserved for
+ * the structure minus the unallocated memory pages between the current end and
+ * the next structure.
+ */
+static Size
+EstimateAllocatedSize(ShmemIndexEnt *entry)
+{
+	Size		page_size = GetOSPageSize();
+	char	   *align_end = (char *) TYPEALIGN(page_size, (char *) entry->location + entry->size);
+	char	   *floor_max_end = (char *) TYPEALIGN_DOWN(page_size, (char *) entry->location + entry->maximum_size);
+
+	Assert(entry->maximum_size >= entry->size);
+	Assert(entry->reserved_space >= entry->maximum_size);
+
+	if (align_end < floor_max_end)
+		return entry->reserved_space - (floor_max_end - align_end);
+
+	return entry->reserved_space;
+}
+
+/*
+ * 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.
+ */
+void
+ShmemResizeStruct(const char *name, Size new_size)
+{
+	ShmemIndexEnt *result;
+	bool		found;
+	Size		page_size = GetOSPageSize();
+	char	   *new_end;
+
+	Assert(new_size > 0);
+
+	/*
+	 * Resizable shared memory structures are only supported with mmap'ed
+	 * memory.
+	 */
+	Assert(shared_memory_type == SHMEM_TYPE_MMAP);
+
+	/* look it up in the shmem index */
+	LWLockAcquire(ShmemIndexLock, LW_EXCLUSIVE);
+	result = (ShmemIndexEnt *) hash_search(ShmemIndex, name, HASH_FIND, &found);
+	if (!found)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("shmem struct \"%s\" is not initialized", name)));
+
+	Assert(result);
+
+	if (result->maximum_size <= 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("shared memory struct \"%s\" is not resizable", name)));
+
+	if (result->maximum_size < new_size)
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_RESOURCES),
+				 errmsg("not enough address space is reserved for resizing structure \"%s\"" \
+						"(required %zu bytes, reserved %zu bytes)",
+						name, new_size, result->maximum_size)));
+
+	/*
+	 * When shrinking the memory from the page aligned new end to the start of
+	 * the page containing end of the reserved space is not required. Whereas
+	 * when expanding the memory from the start of the page containing the
+	 * start of the structure to the page aligned new end is required.
+	 */
+	new_end = (char *) TYPEALIGN(page_size, (char *) result->location + new_size);
+	if (new_size < result->size)
+	{
+		char	   *max_end = (char *) TYPEALIGN_DOWN(page_size, (char *) result->location + result->maximum_size);
+
+		if (max_end > new_end)
+			PGSharedMemoryEnsureFreed(new_end, max_end - new_end);
+	}
+	else if (new_size > result->size)
+	{
+		char	   *struct_start = (char *) TYPEALIGN_DOWN(page_size, (char *) result->location);
+
+		if (new_end > struct_start)
+			PGSharedMemoryEnsureAllocated(struct_start, new_end - struct_start);
+	}
+
+	/* Update shmem index entry. */
+	result->size = new_size;
+	result->allocated_size = EstimateAllocatedSize(result);
+
+	LWLockRelease(ShmemIndexLock);
+}
+#endif							/* HAVE_RESIZABLE_SHMEM */
+
 /*
  *	InitShmemAllocator() --- set up basic pointers to shared memory.
  *
@@ -723,6 +922,10 @@ InitShmemAllocator(PGShmemHeader *seghdr)
 		Assert(!found);
 		result->size = ShmemAllocator->index_size;
 		result->allocated_size = ShmemAllocator->index_size;
+#ifdef HAVE_RESIZABLE_SHMEM
+		result->maximum_size = 0;
+		result->reserved_space = result->allocated_size;
+#endif
 		result->location = ShmemAllocator->index;
 	}
 }
@@ -1064,7 +1267,7 @@ mul_size(Size s1, Size s2)
 Datum
 pg_get_shmem_allocations(PG_FUNCTION_ARGS)
 {
-#define PG_GET_SHMEM_SIZES_COLS 4
+#define PG_GET_SHMEM_SIZES_COLS 6
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	HASH_SEQ_STATUS hstat;
 	ShmemIndexEnt *ent;
@@ -1086,7 +1289,23 @@ 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);
+#ifdef HAVE_RESIZABLE_SHMEM
+		values[4] = Int64GetDatum(ent->maximum_size);
+		values[5] = Int64GetDatum(ent->reserved_space);
+
+		/*
+		 * Keep track of the total reserved space for named shmem areas, to be
+		 * able to calculate the amount of shared memory allocated for
+		 * anonymous areas and the amount of free shared memory at the end of
+		 * the segment.
+		 */
+		named_allocated += ent->reserved_space;
+#else
+		values[4] = Int64GetDatum(0);
+		values[5] = Int64GetDatum(ent->allocated_size);
+
 		named_allocated += ent->allocated_size;
+#endif
 
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
 							 values, nulls);
@@ -1097,6 +1316,8 @@ 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[5] = values[2];
 	tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
 
 	/* output as-of-yet unused shared memory */
@@ -1105,6 +1326,8 @@ 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[5] = values[2];
 	tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
 
 	LWLockRelease(ShmemIndexLock);
@@ -1292,23 +1515,9 @@ pg_get_shmem_allocations_numa(PG_FUNCTION_ARGS)
 Size
 pg_get_shmem_pagesize(void)
 {
-	Size		os_page_size;
-#ifdef WIN32
-	SYSTEM_INFO sysinfo;
-
-	GetSystemInfo(&sysinfo);
-	os_page_size = sysinfo.dwPageSize;
-#else
-	os_page_size = sysconf(_SC_PAGESIZE);
-#endif
-
 	Assert(IsUnderPostmaster);
-	Assert(huge_pages_status != HUGE_PAGES_UNKNOWN);
-
-	if (huge_pages_status == HUGE_PAGES_ON)
-		GetHugePageSize(&os_page_size, NULL);
 
-	return os_page_size;
+	return GetOSPageSize();
 }
 
 Datum
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index a315c4ab8ab..b4d98a1f610 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1211,6 +1211,13 @@
   max => '1000.0',
 },
 
+{ name => 'have_resizable_shmem', type => 'bool', context => 'PGC_INTERNAL', group => 'PRESET_OPTIONS',
+  short_desc => 'Shows whether the running server supports resizable shared memory.',
+  flags => 'GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE',
+  variable => 'have_resizable_shmem_enabled',
+  boot_val => 'HAVE_RESIZABLE_SHMEM_ENABLED',
+},
+
 { name => 'hba_file', type => 'string', context => 'PGC_POSTMASTER', group => 'FILE_LOCATIONS',
   short_desc => 'Sets the server\'s "hba" configuration file.',
   flags => 'GUC_SUPERUSER_ONLY',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index d9ca13baff9..6bb08dd10f1 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -653,6 +653,13 @@ static bool assert_enabled = DEFAULT_ASSERT_ENABLED;
 #endif
 static bool exec_backend_enabled = EXEC_BACKEND_ENABLED;
 
+#ifdef HAVE_RESIZABLE_SHMEM
+#define HAVE_RESIZABLE_SHMEM_ENABLED true
+#else
+#define HAVE_RESIZABLE_SHMEM_ENABLED false
+#endif
+static bool have_resizable_shmem_enabled = HAVE_RESIZABLE_SHMEM_ENABLED;
+
 static char *recovery_target_timeline_string;
 static char *recovery_target_string;
 static char *recovery_target_xid_string;
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index bd177aebfcb..e575d70b572 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8664,8 +8664,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}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,off,size,allocated_size}',
+  proallargtypes => '{text,int8,int8,int8,int8,int8}', proargmodes => '{o,o,o,o,o,o}',
+  proargnames => '{name,off,size,allocated_size,maximum_size,reserved_space}',
   prosrc => 'pg_get_shmem_allocations',
   proacl => '{POSTGRES=X,pg_read_all_stats=X}' },
 
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 9f6d512347e..8f2a59ec3a8 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -85,6 +85,14 @@
    don't. */
 #undef HAVE_DECL_F_FULLFSYNC
 
+/* Define to 1 if you have the declaration of `MADV_POPULATE_WRITE', and to 0
+   if you don't. */
+#undef HAVE_DECL_MADV_POPULATE_WRITE
+
+/* Define to 1 if you have the declaration of `MADV_REMOVE', and to 0 if you
+   don't. */
+#undef HAVE_DECL_MADV_REMOVE
+
 /* Define to 1 if you have the declaration of `memset_s', and to 0 if you
    don't. */
 #undef HAVE_DECL_MEMSET_S
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 521b49b8888..b09d6c91324 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -131,6 +131,15 @@
 #define EXEC_BACKEND
 #endif
 
+/*
+ * HAVE_RESIZABLE_SHMEM indicates whether resizable shared memory structures are
+ * supported. The implementation requires Linux-specific madvise constants
+ * (MADV_REMOVE and MADV_POPULATE_WRITE).
+ */
+#if HAVE_DECL_MADV_REMOVE && HAVE_DECL_MADV_POPULATE_WRITE && !defined(EXEC_BACKEND)
+#define HAVE_RESIZABLE_SHMEM
+#endif
+
 /*
  * USE_POSIX_FADVISE controls whether Postgres will attempt to use the
  * posix_fadvise() kernel call.  Usually the automatic configure tests are
diff --git a/src/include/storage/pg_shmem.h b/src/include/storage/pg_shmem.h
index 10c7b065861..3d5aceba59c 100644
--- a/src/include/storage/pg_shmem.h
+++ b/src/include/storage/pg_shmem.h
@@ -89,6 +89,11 @@ extern PGShmemHeader *PGSharedMemoryCreate(Size size,
 										   PGShmemHeader **shim);
 extern bool PGSharedMemoryIsInUse(unsigned long id1, unsigned long id2);
 extern void PGSharedMemoryDetach(void);
+#ifdef HAVE_RESIZABLE_SHMEM
+extern void PGSharedMemoryEnsureFreed(void *addr, Size size);
+extern void PGSharedMemoryEnsureAllocated(void *addr, Size size);
+#endif
 extern void GetHugePageSize(Size *hugepagesize, int *mmap_flags);
+extern Size GetOSPageSize(void);
 
 #endif							/* PG_SHMEM_H */
diff --git a/src/include/storage/shmem.h b/src/include/storage/shmem.h
index 91218db6d6e..122bf7943ca 100644
--- a/src/include/storage/shmem.h
+++ b/src/include/storage/shmem.h
@@ -57,6 +57,18 @@ typedef struct ShmemStructOpts
 	 */
 	size_t		alignment;
 
+#ifdef HAVE_RESIZABLE_SHMEM
+
+	/*
+	 * Maximum size this structure can grow upto in future. The memory is not
+	 * allocated right away but the corresponding address space is reserved so
+	 * that memory can be mapped to it when the structure grows. Typically
+	 * should be used for large resizable structures which need several pages
+	 * worth of contiguous memory.
+	 */
+	ssize_t		maximum_size;
+#endif
+
 	/*
 	 * When the shmem area is initialized or attached to, pointer to it is
 	 * stored in *ptr.  It usually points to a global variable, used to access
@@ -166,6 +178,9 @@ typedef struct ShmemCallbacks
 
 extern void RegisterShmemCallbacks(const ShmemCallbacks *callbacks);
 extern bool ShmemAddrIsValid(const void *addr);
+#ifdef HAVE_RESIZABLE_SHMEM
+extern void ShmemResizeStruct(const char *name, Size new_size);
+#endif
 
 /*
  * These macros provide syntactic sugar for calling the underlying functions
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index f1b04c99969..2a1e746bf0c 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -14,6 +14,7 @@ SUBDIRS = \
 		  libpq_pipeline \
 		  oauth_validator \
 		  plsample \
+		  resizable_shmem \
 		  spgist_name_ops \
 		  test_aio \
 		  test_binaryheap \
diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build
index fc99552d9ab..cd94e1fea15 100644
--- a/src/test/modules/meson.build
+++ b/src/test/modules/meson.build
@@ -13,6 +13,7 @@ subdir('libpq_pipeline')
 subdir('nbtree')
 subdir('oauth_validator')
 subdir('plsample')
+subdir('resizable_shmem')
 subdir('spgist_name_ops')
 subdir('ssl_passphrase_callback')
 subdir('test_aio')
diff --git a/src/test/modules/resizable_shmem/Makefile b/src/test/modules/resizable_shmem/Makefile
new file mode 100644
index 00000000000..86bf17bef4a
--- /dev/null
+++ b/src/test/modules/resizable_shmem/Makefile
@@ -0,0 +1,25 @@
+# src/test/modules/resizable_shmem/Makefile
+
+PGFILEDESC = "resizable_shmem - test module for resizable shared memory"
+
+MODULES = resizable_shmem
+
+EXTENSION = resizable_shmem
+DATA = resizable_shmem--1.0.sql
+
+TAP_TESTS = 1
+
+# This test requires library to be loaded at the server start, so disable
+# installcheck
+NO_INSTALLCHECK = 1
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/resizable_shmem
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/resizable_shmem/meson.build b/src/test/modules/resizable_shmem/meson.build
new file mode 100644
index 00000000000..493bbbc95c3
--- /dev/null
+++ b/src/test/modules/resizable_shmem/meson.build
@@ -0,0 +1,36 @@
+# src/test/modules/resizable_shmem/meson.build
+
+resizable_shmem_sources = files(
+  'resizable_shmem.c',
+)
+
+if host_system == 'windows'
+  resizable_shmem_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'resizable_shmem',
+    '--FILEDESC', 'resizable_shmem - test module for resizable shared memory',])
+endif
+
+resizable_shmem = shared_module('resizable_shmem',
+  resizable_shmem_sources,
+  kwargs: pg_test_mod_args,
+)
+test_install_libs += resizable_shmem
+
+test_install_data += files(
+  'resizable_shmem.control',
+  'resizable_shmem--1.0.sql',
+)
+
+tests += {
+  'name': 'resizable_shmem',
+  'sd': meson.current_source_dir(),
+  'bd': meson.current_build_dir(),
+  'tap': {
+    'tests': [
+      't/001_resizable_shmem.pl',
+    ],
+    # This test requires library to be loaded at the server start, so disable
+    # installcheck
+    'runningcheck': false,
+  },
+}
diff --git a/src/test/modules/resizable_shmem/resizable_shmem--1.0.sql b/src/test/modules/resizable_shmem/resizable_shmem--1.0.sql
new file mode 100644
index 00000000000..c1bcb6117b6
--- /dev/null
+++ b/src/test/modules/resizable_shmem/resizable_shmem--1.0.sql
@@ -0,0 +1,37 @@
+/* src/test/modules/resizable_shmem/resizable_shmem--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION resizable_shmem" to load this file. \quit
+
+-- Function to resize the test structure in the shared memory
+CREATE FUNCTION resizable_shmem_resize(new_entries integer)
+RETURNS void
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT;
+
+-- Function to write data to all entries in the test structure in shared memory
+-- Writing all the entries makes sure that the memory is actually allocated and
+-- mapped to the process, so that we can later measure the memory usage.
+CREATE FUNCTION resizable_shmem_write(entry_value integer)
+RETURNS void
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT;
+
+-- Function to verify that specified number of initial entries have expected value.
+-- Reading all the entries makes sure that the memory is actually mapped to the
+-- process, so that we can later measure the memory usage.
+CREATE FUNCTION resizable_shmem_read(entry_count integer, entry_value integer)
+RETURNS boolean
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT;
+
+-- Function to report memory usage statistics of the calling backend
+CREATE FUNCTION resizable_shmem_usage(OUT rss_anon bigint, OUT rss_file bigint, OUT rss_shmem bigint, OUT vm_size bigint)
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT;
+
+-- Function to get the shared memory page size
+CREATE FUNCTION resizable_shmem_pagesize()
+RETURNS integer
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT;
diff --git a/src/test/modules/resizable_shmem/resizable_shmem.c b/src/test/modules/resizable_shmem/resizable_shmem.c
new file mode 100644
index 00000000000..970ea75c0de
--- /dev/null
+++ b/src/test/modules/resizable_shmem/resizable_shmem.c
@@ -0,0 +1,332 @@
+/* -------------------------------------------------------------------------
+ *
+ * resizable_shmem.c
+ *		Test module for PostgreSQL's resizable shared memory functionality
+ *
+ * This module demonstrates and tests the resizable shared memory API
+ * provided by shmem.c/shmem.h.
+ *
+ * -------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "commands/extension.h"
+#include "fmgr.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "storage/shmem.h"
+#include "storage/spin.h"
+#include "utils/builtins.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+#include "utils/timestamp.h"
+#include "access/htup_details.h"
+
+#include <stdio.h>
+
+PG_MODULE_MAGIC;
+
+/* Default values for the GUCs controlling structure size */
+#define TEST_INITIAL_ENTRIES_DEFAULT		(25 * 1024 * 1024)	/* ~100MB */
+#define TEST_MAX_ENTRIES_DEFAULT			(100 * 1024 * 1024) /* ~400MB */
+
+#define TEST_ENTRY_SIZE			sizeof(int32)	/* Size of each entry */
+
+/*
+ * Resizable test data structure stored in shared memory.
+ *
+ * The test performs resizing, reads or writes, only one at a time and never
+ * concurrently. Hence, there is no need for locks in the test structure.
+ */
+typedef struct TestResizableShmemStruct
+{
+	/* Metadata */
+	int32		num_entries;	/* Number of entries that can fit */
+
+	/* Data area - variable size */
+	int32		data[FLEXIBLE_ARRAY_MEMBER];
+} TestResizableShmemStruct;
+
+static TestResizableShmemStruct *resizable_shmem = NULL;
+
+/* GUC variables controlling the size of the test structure */
+static int	test_initial_entries;
+static int	test_max_entries;
+
+/* Whether to use SHMEM_ATTACH_UNKNOWN_SIZE when attaching to the shared memory */
+static bool use_unknown_size = false;
+
+static void resizable_shmem_request(void *arg);
+static void resizable_shmem_shmem_init(void *arg);
+
+static ShmemCallbacks shmem_callbacks = {
+	.request_fn = resizable_shmem_request,
+	.init_fn = resizable_shmem_shmem_init,
+};
+
+/* SQL-callable functions */
+PG_FUNCTION_INFO_V1(resizable_shmem_resize);
+PG_FUNCTION_INFO_V1(resizable_shmem_write);
+PG_FUNCTION_INFO_V1(resizable_shmem_read);
+PG_FUNCTION_INFO_V1(resizable_shmem_usage);
+PG_FUNCTION_INFO_V1(resizable_shmem_pagesize);
+
+/*
+ * Module load callback
+ */
+void
+_PG_init(void)
+{
+	int			guc_context;
+
+	/*
+	 * Use PGC_POSTMASTER when loaded at startup so the values are fixed once
+	 * the shared memory segment is created.  When loaded after startup
+	 * PGC_POSTMASTER is not allowed, so we use PGC_SIGHUP instead.  Although
+	 * we do not intend to change these values at config reload, PGC_SIGHUP is
+	 * the least permissive context that allows defining the GUC after startup
+	 * and still prevents it from being changed via SET.
+	 */
+	if (process_shared_preload_libraries_in_progress)
+		guc_context = PGC_POSTMASTER;
+	else
+	{
+		guc_context = PGC_SIGHUP;
+		shmem_callbacks.flags = SHMEM_CALLBACKS_ALLOW_AFTER_STARTUP;
+	}
+
+	DefineCustomIntVariable("resizable_shmem.initial_entries",
+							"Initial number of entries in the test structure.",
+							NULL,
+							&test_initial_entries,
+							TEST_INITIAL_ENTRIES_DEFAULT,
+							1,
+							INT_MAX,
+							guc_context,
+							0,
+							NULL, NULL, NULL);
+
+	DefineCustomIntVariable("resizable_shmem.max_entries",
+							"Maximum number of entries in the test structure.",
+							NULL,
+							&test_max_entries,
+							TEST_MAX_ENTRIES_DEFAULT,
+							1,
+							INT_MAX,
+							guc_context,
+							0,
+							NULL, NULL, NULL);
+
+	/*
+	 * When loaded after startup by a backend that is not creating the
+	 * extension, the shared memory might have been resized to a size other
+	 * than the initial size. Use SHMEM_ATTACH_UNKNOWN_SIZE to attach without
+	 * knowing the exact size.
+	 */
+	if (!process_shared_preload_libraries_in_progress && !creating_extension)
+		use_unknown_size = true;
+
+	RegisterShmemCallbacks(&shmem_callbacks);
+}
+
+/*
+ * Request shared memory resources
+ */
+static void
+resizable_shmem_request(void *arg)
+{
+	Size		initial_size = add_size(offsetof(TestResizableShmemStruct, data),
+										mul_size(test_initial_entries, TEST_ENTRY_SIZE));
+#ifdef HAVE_RESIZABLE_SHMEM
+	Size		max_size = add_size(offsetof(TestResizableShmemStruct, data),
+									mul_size(test_max_entries, TEST_ENTRY_SIZE));
+
+	/* A preprocessor macro to conditionally include the maximum_size field. */
+#define MAXIMUM_SIZE_ARG		.maximum_size = max_size,
+#else
+#define MAXIMUM_SIZE_ARG
+#endif
+
+	/* Register our resizable shared memory structure */
+	ShmemRequestStruct(.name = "resizable_shmem",
+					   .size = use_unknown_size ? SHMEM_ATTACH_UNKNOWN_SIZE : initial_size,
+					   MAXIMUM_SIZE_ARG
+					   .ptr = (void **) &resizable_shmem,
+		);
+}
+
+/*
+ * Initialize shared memory structure
+ */
+static void
+resizable_shmem_shmem_init(void *arg)
+{
+	/*
+	 * Shared memory structure should have been already allocated. Initialize
+	 * it.
+	 */
+	Assert(resizable_shmem != NULL);
+
+	resizable_shmem->num_entries = test_initial_entries;
+	memset(resizable_shmem->data, 0, mul_size(test_initial_entries, TEST_ENTRY_SIZE));
+}
+
+/*
+ * Resize the shared memory structure to accommodate the specified number of
+ * entries.
+ */
+Datum
+resizable_shmem_resize(PG_FUNCTION_ARGS)
+{
+#ifdef HAVE_RESIZABLE_SHMEM
+	int32		new_entries = PG_GETARG_INT32(0);
+	Size		new_size;
+
+	if (!resizable_shmem)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("resizable_shmem is not initialized")));
+
+	new_size = add_size(offsetof(TestResizableShmemStruct, data),
+						mul_size(new_entries, TEST_ENTRY_SIZE));
+	ShmemResizeStruct("resizable_shmem", new_size);
+	resizable_shmem->num_entries = new_entries;
+
+	PG_RETURN_VOID();
+#else
+	ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("resizable shared memory is not supported on this platform")));
+#endif
+}
+
+/*
+ * Write the given integer value to all entries in the data array.
+ */
+Datum
+resizable_shmem_write(PG_FUNCTION_ARGS)
+{
+	int32		entry_value = PG_GETARG_INT32(0);
+	int32		i;
+
+	if (!resizable_shmem)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("resizable_shmem is not initialized")));
+
+	/* Write the value to all current entries */
+	for (i = 0; i < resizable_shmem->num_entries; i++)
+		resizable_shmem->data[i] = entry_value;
+
+	PG_RETURN_VOID();
+}
+
+/*
+ * Check whether the first 'entry_count' entries all have the expected 'entry_value'.
+ * Returns true if all match, false otherwise.
+ */
+Datum
+resizable_shmem_read(PG_FUNCTION_ARGS)
+{
+	int32		entry_count = PG_GETARG_INT32(0);
+	int32		entry_value = PG_GETARG_INT32(1);
+	int32		i;
+
+	if (resizable_shmem == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("resizable_shmem is not initialized")));
+
+	if (entry_count < 0 || entry_count > resizable_shmem->num_entries)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("entry_count %d is out of range (0..%d)", entry_count, resizable_shmem->num_entries)));
+
+	for (i = 0; i < entry_count; i++)
+	{
+		if (resizable_shmem->data[i] != entry_value)
+			PG_RETURN_BOOL(false);
+	}
+
+	PG_RETURN_BOOL(true);
+}
+
+/*
+ * Report multiple memory usage statistics of the calling backend process
+ * as reported by the kernel.
+ * Returns RssAnon, RssFile, RssShmem, VmSize from /proc/self/status as a record.
+ *
+ * The function assumes that these values will be available in
+ * /proc/self/status, any system which also support madvise with MADV_REMOVE and
+ * MADV_POPULATE_WRITE.
+ */
+Datum
+resizable_shmem_usage(PG_FUNCTION_ARGS)
+{
+#ifdef HAVE_RESIZABLE_SHMEM
+	FILE	   *f;
+	char		line[256];
+	int64		rss_anon_kb = -1;
+	int64		rss_file_kb = -1;
+	int64		rss_shmem_kb = -1;
+	int64		vm_size_kb = -1;
+	int			found = 0;
+	TupleDesc	tupdesc;
+	Datum		values[4];
+	bool		nulls[4];
+	HeapTuple	tuple;
+
+	/* Open /proc/self/status to read memory information */
+	f = fopen("/proc/self/status", "r");
+	if (f == NULL)
+		ereport(ERROR,
+				(errcode_for_file_access(),
+				 errmsg("could not open /proc/self/status: %m")));
+
+	/* Look for the memory usage lines */
+	while (fgets(line, sizeof(line), f) != NULL && found < 4)
+	{
+		if (rss_anon_kb == -1 && sscanf(line, "RssAnon: %ld kB", &rss_anon_kb) == 1)
+			found++;
+		else if (rss_file_kb == -1 && sscanf(line, "RssFile: %ld kB", &rss_file_kb) == 1)
+			found++;
+		else if (rss_shmem_kb == -1 && sscanf(line, "RssShmem: %ld kB", &rss_shmem_kb) == 1)
+			found++;
+		else if (vm_size_kb == -1 && sscanf(line, "VmSize: %ld kB", &vm_size_kb) == 1)
+			found++;
+	}
+
+	fclose(f);
+
+	/* Build tuple descriptor for our result type */
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("function returning record called in context "
+						"that cannot accept a record")));
+
+	/* Build the result tuple */
+	values[0] = Int64GetDatum(rss_anon_kb >= 0 ? rss_anon_kb * 1024 : 0);
+	values[1] = Int64GetDatum(rss_file_kb >= 0 ? rss_file_kb * 1024 : 0);
+	values[2] = Int64GetDatum(rss_shmem_kb >= 0 ? rss_shmem_kb * 1024 : 0);
+	values[3] = Int64GetDatum(vm_size_kb >= 0 ? vm_size_kb * 1024 : 0);
+
+	nulls[0] = nulls[1] = nulls[2] = nulls[3] = false;
+
+	tuple = heap_form_tuple(tupdesc, values, nulls);
+	PG_RETURN_DATUM(HeapTupleGetDatum(tuple));
+#else
+	ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("resizable shared memory is not supported on this platform")));
+#endif
+}
+
+/*
+ * resizable_shmem_pagesize() - Get the shared memory page size
+ */
+Datum
+resizable_shmem_pagesize(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT32(pg_get_shmem_pagesize());
+}
diff --git a/src/test/modules/resizable_shmem/resizable_shmem.control b/src/test/modules/resizable_shmem/resizable_shmem.control
new file mode 100644
index 00000000000..8031303fe0e
--- /dev/null
+++ b/src/test/modules/resizable_shmem/resizable_shmem.control
@@ -0,0 +1,4 @@
+# resizable_shmem extension test module
+comment = 'test module for testing resizable shared memory structure functionality'
+default_version = '1.0'
+module_pathname = '$libdir/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
new file mode 100644
index 00000000000..6d45b1eccdc
--- /dev/null
+++ b/src/test/modules/resizable_shmem/t/001_resizable_shmem.pl
@@ -0,0 +1,239 @@
+# Copyright (c) 2025-2026, PostgreSQL Global Development Group
+
+use strict;
+use warnings FATAL => 'all';
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# Test resizable shared memory functionality, both when loaded at startup via
+# shared_preload_libraries and when loaded after startup (late allocation).
+
+# Verify that RssShmem does not exceed the total allocated shared memory.
+# Allocated shared memory should be mostly the memory allocated to the
+# resizable_shmem structure. Any large increase in expected RssShmem should
+# reflect the unexpected increase in memory allocated to the resizable_shmem
+# structure.
+sub check_shmem_usage
+{
+	my ($session, $label, $node) = @_;
+
+	my $rss_shmem = $session->query_safe('SELECT rss_shmem FROM resizable_shmem_usage();',
+										 verbose => 0);
+	my $total_alloc = $node->safe_psql('postgres',
+									   "SELECT sum(allocated_size) FROM pg_shmem_allocations;");
+
+	note "$label: RssShmem=$rss_shmem, sum(allocated_size)=$total_alloc";
+	ok($rss_shmem <= $total_alloc, "$label: RssShmem does not exceed total allocated size");
+}
+
+# Test a resize operation: resize, verify old data, write new data, verify
+# new data, and check shmem usage.  Returns updated ($num_entries, $value).
+sub test_resize
+{
+	my ($node, $prefix, $old_num_entries, $old_value, $new_num_entries, $new_value, $label) = @_;
+
+	$label = "$prefix: $label";
+
+	my $session1 = $node->background_psql('postgres');
+	my $session2 = $node->background_psql('postgres');
+
+	$session1->query_safe("SELECT resizable_shmem_resize($new_num_entries);",
+						  verbose => 0);
+
+	# Old data should still be intact in the (possibly smaller) area
+	my $readable_entries = ($new_num_entries < $old_num_entries) ? $new_num_entries : $old_num_entries;
+	is($session1->query_safe("SELECT resizable_shmem_read($readable_entries, $old_value);",
+							 verbose => 0),
+	   't', "old data readable after $label");
+
+	$session2->query_safe("SELECT resizable_shmem_write($new_value);",
+						  verbose => 0);
+	is($session1->query_safe("SELECT resizable_shmem_read($new_num_entries, $new_value);",
+							 verbose => 0),
+	   't', "new data readable after $label");
+
+	check_shmem_usage($session1, "$label (session 1)", $node);
+	check_shmem_usage($session2, "$label (session 2)", $node);
+
+	$session1->quit;
+	$session2->quit;
+
+	return ($new_num_entries, $new_value);
+}
+
+# Run the full suite of resizable shared memory tests on the given node.
+sub run_resizable_tests
+{
+	my ($node, $initial_entries, $max_entries, $prefix) = @_;
+
+	my $have_resizable_shmem = $node->safe_psql('postgres', 'SHOW have_resizable_shmem;') eq 'on';
+
+	my $num_entries = $initial_entries;
+
+	# Basic read/write should work on all platforms
+	my $value = 100;
+	$node->safe_psql('postgres', "SELECT resizable_shmem_write($value);");
+	is($node->safe_psql('postgres', "SELECT resizable_shmem_read($num_entries, $value);"),
+	   't', "$prefix: data read after write successful");
+
+	if ($have_resizable_shmem)
+	{
+		# Initial structure state
+		my $session1 = $node->background_psql('postgres');
+		my $session2 = $node->background_psql('postgres');
+
+		$value = 100;
+		# Write and read the initial set of entries.
+		$session1->query_safe("SELECT resizable_shmem_write($value);", verbose => 0);
+		is($session2->query_safe("SELECT resizable_shmem_read($num_entries, $value);",
+								 verbose => 0),
+		   't', "$prefix: data read after write successful");
+		check_shmem_usage($session1, "$prefix: initial write (session 1)", $node);
+		check_shmem_usage($session2, "$prefix: initial write (session 2)", $node);
+		$session1->quit;
+		$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;"),
+							'0', "$prefix: no other resizable structures");
+
+		# Resize to maximum
+		($num_entries, $value) = test_resize($node, $prefix, $num_entries, $value,
+											 $max_entries, 500, 'resize to maximum');
+
+		# Shrink to 75% of max
+		my $shrink_entries = int($max_entries * 3 / 4);
+		($num_entries, $value) = test_resize($node, $prefix, $num_entries, $value,
+											 $shrink_entries, 999, 'shrinking');
+
+		# Resize to the same size (no-op)
+		($num_entries, $value) = test_resize($node, $prefix, $num_entries, $value,
+											 $num_entries, 1999, 'no-op resize');
+
+		# Test resize failure (attempt to resize beyond max - should fail)
+		my ($ret, $stdout, $stderr) =
+			$node->psql('postgres', "SELECT resizable_shmem_resize(" . ($max_entries * 2) . ");");
+		ok($ret != 0 || $stderr =~ /ERROR/, "$prefix: Resize beyond maximum fails");
+	}
+	else
+	{
+		# On unsupported platforms, resizing should fail with a clear error
+		my ($ret, $stdout, $stderr) =
+			$node->psql('postgres', "SELECT resizable_shmem_resize($num_entries);");
+		ok($ret != 0, "$prefix: resize fails on unsupported platform");
+		like($stderr, qr/not supported/, "$prefix: resize error mentions not supported");
+	}
+}
+
+### Set up a test node.
+#
+#Configure minimal shared memory so that the resizable_shmem structure dominates
+#and any unexpected increase is easy to detect.
+#
+# Also disable huge pages so that RssShmem and allocated_size are comparable.
+# The latter is already aligned to the default page size.
+###
+my $node = PostgreSQL::Test::Cluster->new('resizable_shmem');
+$node->init;
+
+$node->append_conf('postgresql.conf', 'huge_pages = off');
+$node->append_conf('postgresql.conf', 'shared_buffers = 128kB');
+$node->append_conf('postgresql.conf', 'max_connections = 5');
+$node->append_conf('postgresql.conf', 'max_worker_processes = 0');
+$node->append_conf('postgresql.conf', 'max_wal_senders = 0');
+$node->append_conf('postgresql.conf', 'max_prepared_transactions = 0');
+$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');
+
+###
+# Test 1: Startup allocation via shared_preload_libraries
+###
+my $startup_initial = 25 * 1024 * 1024;
+my $startup_max = 100 * 1024 * 1024;
+
+$node->append_conf('postgresql.conf', 'shared_preload_libraries = resizable_shmem');
+$node->append_conf('postgresql.conf', "resizable_shmem.initial_entries = $startup_initial");
+$node->append_conf('postgresql.conf', "resizable_shmem.max_entries = $startup_max");
+$node->start;
+$node->safe_psql('postgres', 'CREATE EXTENSION resizable_shmem;');
+run_resizable_tests($node, $startup_initial, $startup_max, 'startup');
+
+my $have_resizable_shmem = $node->safe_psql('postgres', 'SHOW have_resizable_shmem;') eq 'on';
+
+###
+# Test 2: Late allocation (loaded after startup, not in shared_preload_libraries).
+# Use much smaller sizes since only ~100KB of shared memory is available for
+# structures allocated after startup.
+###
+my $late_initial = 5 * 1024;
+my $late_max = 12 * 1024;
+
+$node->safe_psql('postgres', qq{
+	ALTER SYSTEM RESET shared_preload_libraries;
+	ALTER SYSTEM SET resizable_shmem.initial_entries = $late_initial;
+	ALTER SYSTEM SET resizable_shmem.max_entries = $late_max;
+});
+$node->safe_psql('postgres', 'DROP EXTENSION resizable_shmem;');
+$node->restart;
+
+$node->safe_psql('postgres', 'CREATE EXTENSION resizable_shmem;');
+run_resizable_tests($node, $late_initial, $late_max, 'late');
+
+###
+# Test sysv shared memory does not support resizable shmem.  Only relevant on
+# platforms that support resizable shmem (HAVE_RESIZABLE_SHMEM), since the
+# module only sets maximum_size in that case.
+###
+if ($have_resizable_shmem)
+{
+	###
+	# Test 3: Verify that CREATE EXTENSION fails with sysv shared memory
+	# when loaded after startup (not in shared_preload_libraries).
+	###
+	$node->safe_psql('postgres', 'DROP EXTENSION resizable_shmem;');
+
+	# Remove settings that would cause the library to auto-load at startup:
+	# shared_preload_libraries and module-prefixed GUCs.  ALTER SYSTEM RESET
+	# only affects postgresql.auto.conf, so we must use adjust_conf to remove
+	# from postgresql.conf.
+	$node->adjust_conf('postgresql.conf', 'shared_preload_libraries', undef);
+	$node->adjust_conf('postgresql.conf', 'resizable_shmem.initial_entries', undef);
+	$node->adjust_conf('postgresql.conf', 'resizable_shmem.max_entries', undef);
+	$node->adjust_conf('postgresql.auto.conf', 'shared_preload_libraries', undef);
+	$node->adjust_conf('postgresql.auto.conf', 'resizable_shmem.initial_entries', undef);
+	$node->adjust_conf('postgresql.auto.conf', 'resizable_shmem.max_entries', undef);
+	$node->safe_psql('postgres', qq{
+		ALTER SYSTEM SET shared_memory_type = 'sysv';
+	});
+
+	$node->restart;
+
+	my ($ret, $stdout, $stderr) =
+		$node->psql('postgres', 'CREATE EXTENSION resizable_shmem;');
+	ok($ret != 0, 'CREATE EXTENSION fails with resizable shmem on sysv');
+	like($stderr, qr/resizable shared memory requires shared_memory_type = mmap/,
+		'CREATE EXTENSION error mentions shared_memory_type = mmap requirement');
+
+	###
+	# Test 4: Verify that resizable structures are also rejected with sysv
+	# shared memory when loaded at startup via shared_preload_libraries.
+	###
+	$node->safe_psql('postgres', qq{
+		ALTER SYSTEM SET shared_preload_libraries = 'resizable_shmem';
+		ALTER SYSTEM SET resizable_shmem.initial_entries = $startup_initial;
+		ALTER SYSTEM SET resizable_shmem.max_entries = $startup_max;
+	});
+	$node->stop;
+
+	ok(!$node->start(fail_ok => 1),
+		'server fails to start with resizable shmem on sysv');
+
+	my $log = slurp_file($node->logfile);
+	like($log, qr/resizable shared memory requires shared_memory_type = mmap/,
+		'log mentions shared_memory_type = mmap requirement');
+}
+
+done_testing();
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 c154f57682a..c89b140871f 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
@@ -45,5 +45,28 @@ else
    ok($attach_count1 == 0 && $attach_count2 == 0, "attach callback is not called when loaded via shared_preload_libraries");
 }
 
+###
+# Test that a fixed-size shared memory structure cannot be resized.
+# Only relevant on platforms that support resizable shmem.
+###
+my $have_resizable_shmem =
+  $node->safe_psql('postgres', 'SHOW have_resizable_shmem;') eq 'on';
+
+if ($have_resizable_shmem)
+{
+   # Try expanding the fixed-size structure
+   my ($ret, $stdout, $stderr) =
+     $node->psql("postgres", "SELECT test_shmem_resize_fixed(1000);");
+   isnt($ret, 0, "expanding a fixed-size structure fails");
+   like($stderr, qr/is not resizable/, "expand error message mentions not resizable");
+
+   # Try shrinking the fixed-size structure
+   ($ret, $stdout, $stderr) =
+     $node->psql("postgres", "SELECT test_shmem_resize_fixed(1);");
+   isnt($ret, 0, "shrinking a fixed-size structure fails");
+   like($stderr, qr/is not resizable/, "shrink error message mentions not resizable");
+}
+
 $node->stop;
+
 done_testing();
diff --git a/src/test/modules/test_shmem/test_shmem--1.0.sql b/src/test/modules/test_shmem/test_shmem--1.0.sql
index 2d01fd9256c..e169d0d7733 100644
--- a/src/test/modules/test_shmem/test_shmem--1.0.sql
+++ b/src/test/modules/test_shmem/test_shmem--1.0.sql
@@ -7,3 +7,7 @@
 CREATE FUNCTION get_test_shmem_attach_count()
 RETURNS pg_catalog.int4 STRICT
 AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_shmem_resize_fixed(pg_catalog.int4)
+RETURNS pg_catalog.void STRICT
+AS 'MODULE_PATHNAME' LANGUAGE C;
diff --git a/src/test/modules/test_shmem/test_shmem.c b/src/test/modules/test_shmem/test_shmem.c
index 9bd4012b435..fc2fd67887f 100644
--- a/src/test/modules/test_shmem/test_shmem.c
+++ b/src/test/modules/test_shmem/test_shmem.c
@@ -99,3 +99,23 @@ get_test_shmem_attach_count(PG_FUNCTION_ARGS)
 		elog(ERROR, "shmem area not yet initialized");
 	PG_RETURN_INT32(TestShmem->attach_count);
 }
+
+/*
+ * Attempt to resize the fixed-size shared memory structure.  This should
+ * fail because the structure was not allocated with a maximum_size.
+ */
+PG_FUNCTION_INFO_V1(test_shmem_resize_fixed);
+Datum
+test_shmem_resize_fixed(PG_FUNCTION_ARGS)
+{
+#ifdef HAVE_RESIZABLE_SHMEM
+	int32		new_size = PG_GETARG_INT32(0);
+
+	ShmemResizeStruct("test_shmem area", new_size);
+	PG_RETURN_VOID();
+#else
+	ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("resizable shared memory is not supported on this platform")));
+#endif
+}
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 81a73c426d2..2bbbf48c96a 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1770,8 +1770,10 @@ pg_shadow| SELECT pg_authid.rolname AS usename,
 pg_shmem_allocations| SELECT name,
     off,
     size,
-    allocated_size
-   FROM pg_get_shmem_allocations() pg_get_shmem_allocations(name, off, size, allocated_size);
+    allocated_size,
+    maximum_size,
+    reserved_space
+   FROM pg_get_shmem_allocations() pg_get_shmem_allocations(name, off, size, allocated_size, maximum_size, reserved_space);
 pg_shmem_allocations_numa| SELECT name,
     numa_node,
     size
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3c35097361d..81bf12cbc1a 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3146,6 +3146,7 @@ TestDSMRegistryHashEntry
 TestDSMRegistryStruct
 TestDecodingData
 TestDecodingTxnData
+TestResizableShmemStruct
 TestShmemData
 TestSpec
 TestValueType
-- 
2.34.1

