From 368db3bdce7f08795ea1271ed02860366633b66e 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 v20260407 1/2] 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>
Reviewed-by: Matthias van de Meent <boekewurm+postgres@gmail.com>
---
 configure.ac                                  |   4 +
 doc/src/sgml/config.sgml                      |  15 +
 doc/src/sgml/system-views.sgml                |  42 ++-
 doc/src/sgml/xfunc.sgml                       |  54 +++
 meson.build                                   |  16 +
 src/backend/port/sysv_shmem.c                 |  79 +++++
 src/backend/port/win32_shmem.c                |  45 +++
 src/backend/storage/ipc/ipci.c                |  11 +
 src/backend/storage/ipc/shmem.c               | 318 ++++++++++++++++--
 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                |   3 +
 src/include/storage/shmem.h                   |  17 +
 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  |  39 +++
 .../modules/resizable_shmem/resizable_shmem.c | 317 +++++++++++++++++
 .../resizable_shmem/resizable_shmem.control   |   4 +
 .../resizable_shmem/t/001_resizable_shmem.pl  | 241 +++++++++++++
 .../test_shmem/t/001_late_shmem_alloc.pl      |  31 ++
 .../modules/test_shmem/test_shmem--1.0.sql    |   4 +
 src/test/modules/test_shmem/test_shmem.c      |  14 +
 src/test/regress/expected/rules.out           |   7 +-
 src/tools/pgindent/typedefs.list              |   1 +
 29 files changed, 1323 insertions(+), 37 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 3324d2d3c49..9f630b1a074 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -12138,6 +12138,21 @@ dynamic_library_path = '/usr/local/lib/postgresql:$libdir'
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-have-resizable-shared-memory" xreflabel="have_resizable_shared_memory">
+      <term><varname>have_resizable_shared_memory</varname> (<type>boolean</type>)
+      <indexterm>
+       <primary><varname>have_resizable_shared_memory</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..9bbbfdb37c5 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -4243,8 +4243,46 @@ 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>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. 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>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 789cac9fcab..22f953db9d7 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -3744,6 +3744,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.
+     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
+     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 <parameter>name</parameter> and 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
+    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 sybsystem using this feature needs to take care of the
+    additional synchronization between the resizing process and the processes
+    using the shared structure. Also it needs to implement additional protection
+    to prevent access to the part of the address space beyond the size of the
+    structure when resizing it.
+    </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-shared-memory"/> 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..bb2a81417c6 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,61 @@ PGSharedMemoryDetach(void)
 		AnonymousShmem = NULL;
 	}
 }
+
+/*
+ * 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)
+{
+#ifndef HAVE_RESIZABLE_SHMEM
+	ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("resizable shared memory is not supported on this platform")));
+#else
+	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")));
+#endif
+}
+
+/*
+ * 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)
+{
+#ifndef HAVE_RESIZABLE_SHMEM
+	ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("resizable shared memory is not supported on this platform")));
+#else
+	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
+}
diff --git a/src/backend/port/win32_shmem.c b/src/backend/port/win32_shmem.c
index 794e4fcb2ad..c1f30665e66 100644
--- a/src/backend/port/win32_shmem.c
+++ b/src/backend/port/win32_shmem.c
@@ -648,3 +648,48 @@ 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;
+}
+
+/*
+ * PGSharedMemoryEnsureFreed / PGSharedMemoryEnsureAllocated
+ *
+ * Not supported on Windows.  These are only meaningful on platforms with
+ * resizable shared memory (mmap + madvise).
+ */
+void
+PGSharedMemoryEnsureFreed(void *addr, Size size)
+{
+	ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("resizable shared memory is not supported on this platform")));
+}
+
+void
+PGSharedMemoryEnsureAllocated(void *addr, Size size)
+{
+	ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("resizable shared memory is not supported on this platform")));
+}
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index bf6b81e621b..4c6ece598b1 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -192,6 +192,17 @@ InitializeShmemGUCs(void)
 	Size		size_b;
 	Size		size_mb;
 	Size		hp_size;
+	bool		have_resizable_shmem;
+
+	/* Does this server support resizable shared memory? */
+#ifdef HAVE_RESIZABLE_SHMEM
+	have_resizable_shmem = (shared_memory_type == SHMEM_TYPE_MMAP);
+#else
+	have_resizable_shmem = false;
+#endif
+	SetConfigOption("have_resizable_shared_memory",
+					have_resizable_shmem ? "on" : "off",
+					PGC_INTERNAL, PGC_S_DYNAMIC_DEFAULT);
 
 	/*
 	 * Calculate the shared memory size and round up to the nearest megabyte.
diff --git a/src/backend/storage/ipc/shmem.c b/src/backend/storage/ipc/shmem.c
index 1ebffe5a32a..8f006967790 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
@@ -102,6 +102,24 @@
  * (*options->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.
+ * 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.
+ *
  * Legacy ShmemInitStruct()/ShmemInitHash() functions
  * --------------------------------------------------
  *
@@ -167,6 +185,16 @@ 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. Please note that on the platforms that do not support
+ * resizable shmem, the maximum_size is ensured to be 0 i.e. all the structures
+ * are treated as fixed-size structures.
+ */
+#define SHMEM_REQUEST_SPACE_SIZE(request) \
+	((request)->options->maximum_size > 0 ? (request)->options->maximum_size : (request)->options->size)
+
 static List *pending_shmem_requests;
 
 /*
@@ -269,6 +297,10 @@ 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;
 
 /* To get reliable results for NUMA inquiry we need to "touch pages" once */
@@ -277,6 +309,7 @@ static bool firstNumaTouch = true;
 static void CallShmemCallbacksAfterStartup(const ShmemCallbacks *callbacks);
 static void InitShmemIndexEntry(ShmemRequest *request);
 static bool AttachShmemIndexEntry(ShmemRequest *request, bool missing_ok);
+static Size EstimateAllocatedSize(ShmemIndexEnt *entry);
 
 Datum		pg_numa_available(PG_FUNCTION_ARGS);
 
@@ -342,11 +375,25 @@ ShmemRequestInternal(ShmemStructOpts *options, ShmemRequestKind kind)
 	if (options->name == NULL)
 		elog(ERROR, "shared memory request is missing 'name' option");
 
+#ifndef HAVE_RESIZABLE_SHMEM
+	if (options->maximum_size > 0)
+		elog(ERROR, "resizable shared memory is not supported on this platform");
+#else
+	if (options->maximum_size > 0 && shared_memory_type != SHMEM_TYPE_MMAP)
+		elog(ERROR, "resizable shared memory requires shared_memory_type = mmap");
+#endif
+
 	if (IsUnderPostmaster)
 	{
 		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);
 	}
 	else
 	{
@@ -355,12 +402,36 @@ 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)
+			elog(ERROR, "invalid maximum_size %zd for shared memory request for \"%s\"",
+				 options->maximum_size, options->name);
 	}
 
 	if (options->alignment != 0 && pg_nextpower2_size_t(options->alignment) != options->alignment)
 		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");
@@ -382,8 +453,8 @@ 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.
  *
  * This is called at postmaster startup, before the shared memory segment has
  * been created.
@@ -408,7 +479,7 @@ ShmemGetRequestedSize(void)
 			alignment = PG_CACHE_LINE_SIZE;
 		size = TYPEALIGN(alignment, size);
 
-		size = add_size(size, request->options->size);
+		size = add_size(size, SHMEM_REQUEST_SPACE_SIZE(request));
 	}
 
 	return size;
@@ -515,6 +586,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 */
@@ -532,10 +604,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)
@@ -544,13 +624,27 @@ 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;
+	index_entry->reserved_space = allocated_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)
@@ -595,7 +689,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)
 	{
@@ -605,6 +699,40 @@ AttachShmemIndexEntry(ShmemRequest *request, bool missing_ok)
 						name, index_entry->size, request->options->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)
+	{
+		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);
+	}
+
 	/*
 	 * Re-establish the caller's pointer variable, or do other actions to
 	 * attach depending on the kind of shmem area it is.
@@ -626,6 +754,127 @@ AttachShmemIndexEntry(ShmemRequest *request, bool missing_ok)
 	return true;
 }
 
+/*
+ * 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.
+ *
+ * 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)
+{
+#ifndef HAVE_RESIZABLE_SHMEM
+	ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("resizable shared memory is not supported on this platform")));
+#else
+	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->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\""
+						"(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
+}
+
 /*
  *	InitShmemAllocator() --- set up basic pointers to shared memory.
  *
@@ -732,6 +981,11 @@ InitShmemAllocator(PGShmemHeader *seghdr)
 		Assert(!found);
 		result->size = ShmemAllocator->index_size;
 		result->allocated_size = ShmemAllocator->index_size;
+#ifdef HAVE_RESIZABLE_SHMEM
+		result->minimum_size = result->size;
+		result->maximum_size = result->size;
+		result->reserved_space = result->allocated_size;
+#endif
 		result->location = ShmemAllocator->index;
 	}
 }
@@ -1075,7 +1329,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 7
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	HASH_SEQ_STATUS hstat;
 	ShmemIndexEnt *ent;
@@ -1097,7 +1351,17 @@ 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);
-		named_allocated += ent->allocated_size;
+		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
+		 * 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;
 
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
 							 values, nulls);
@@ -1108,6 +1372,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] = 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 */
@@ -1116,6 +1383,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] = values[2];
+	values[5] = values[2];
+	values[6] = values[2];
 	tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
 
 	LWLockRelease(ShmemIndexLock);
@@ -1303,23 +1573,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 fcb6ab80583..22b7e461d3a 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1219,6 +1219,13 @@
   max => '1000.0',
 },
 
+{ name => 'have_resizable_shared_memory', 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_shared_memory_enabled',
+  boot_val => 'HAVE_RESIZABLE_SHARED_MEMORY_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..924f95a4a70 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_SHARED_MEMORY_ENABLED true
+#else
+#define HAVE_RESIZABLE_SHARED_MEMORY_ENABLED false
+#endif
+static bool have_resizable_shared_memory_enabled = HAVE_RESIZABLE_SHARED_MEMORY_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 99fa9a6ede2..3a622525dfc 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8709,8 +8709,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,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/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..f0efbf2aec1 100644
--- a/src/include/storage/pg_shmem.h
+++ b/src/include/storage/pg_shmem.h
@@ -89,6 +89,9 @@ extern PGShmemHeader *PGSharedMemoryCreate(Size size,
 										   PGShmemHeader **shim);
 extern bool PGSharedMemoryIsInUse(unsigned long id1, unsigned long id2);
 extern void PGSharedMemoryDetach(void);
+extern void PGSharedMemoryEnsureFreed(void *addr, Size size);
+extern void PGSharedMemoryEnsureAllocated(void *addr, Size size);
 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 af7fe893bc4..0e6d5a63f28 100644
--- a/src/include/storage/shmem.h
+++ b/src/include/storage/shmem.h
@@ -57,6 +57,22 @@ 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
+	 * 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. Should be set to 0 for fixed-size
+	 * structures.
+	 */
+	ssize_t		maximum_size;
+
 	/*
 	 * 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
@@ -168,6 +184,7 @@ typedef struct ShmemCallbacks
 
 extern void RegisterShmemCallbacks(const ShmemCallbacks *callbacks);
 extern bool ShmemAddrIsValid(const void *addr);
+extern void ShmemResizeStruct(const char *name, Size new_size);
 
 /*
  * These macros provide syntactic sugar for calling the underlying functions
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 0a74ab5c86f..fa29f486354 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_autovacuum \
diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build
index 4bca42bb370..d69c37d3d6a 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..b4b07336dc3
--- /dev/null
+++ b/src/test/modules/resizable_shmem/resizable_shmem--1.0.sql
@@ -0,0 +1,39 @@
+/* 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 mapped against the main shared memory segment in
+-- the backend where this function runs.
+CREATE FUNCTION resizable_shmem_usage()
+RETURNS 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..6fd9e02f7b3
--- /dev/null
+++ b/src/test/modules/resizable_shmem/resizable_shmem.c
@@ -0,0 +1,317 @@
+/* -------------------------------------------------------------------------
+ *
+ * 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 <limits.h>
+#include <stdio.h>
+
+#include "commands/extension.h"
+#include "fmgr.h"
+#include "miscadmin.h"
+#include "storage/shmem.h"
+#include "storage/spin.h"
+#include "utils/builtins.h"
+#include "utils/guc.h"
+#include "utils/memutils.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));
+
+/*
+ * Create resizable structure on the platforms which support it. Otherwise create
+ * as a fixed-size structure. Other way would be to conditionally include
+ * .maximum_size in the call to ShmemRequestStruct().
+ */
+#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,
+		);
+}
+
+/*
+ * 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.
+ *
+ * On the plaforms which do not support resizable shared memory,
+ * ShmemResizeStruct() will raise an error, so this function will fail if the
+ * caller tries to resize the structure.
+ */
+Datum
+resizable_shmem_resize(PG_FUNCTION_ARGS)
+{
+	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();
+}
+
+/*
+ * 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);
+}
+
+/*
+ * Return the memory mapped against the main shared memory segment in this
+ * backend.
+ *
+ * The VMA containing our resizable_shmem pointer is used to determine the main
+ * memory segment.  RSS + Swap (in bytes) for that VMS from  /proc/self/smaps is
+ * returned.
+ */
+Datum
+resizable_shmem_usage(PG_FUNCTION_ARGS)
+{
+	FILE	   *f;
+	char		line[256];
+	int64		rss_kb = -1;
+	int64		swap_kb = -1;
+	uintptr_t	target = (uintptr_t) resizable_shmem;
+	bool		in_target_vma = false;
+	size_t		result;
+
+	f = fopen("/proc/self/smaps", "r");
+	if (f == NULL)
+		ereport(ERROR,
+				(errcode_for_file_access(),
+				 errmsg("could not open /proc/self/smaps: %m")));
+
+	while (fgets(line, sizeof(line), f) != NULL)
+	{
+		unsigned long start;
+		unsigned long end;
+
+		if (sscanf(line, "%lx-%lx", &start, &end) == 2)
+		{
+			in_target_vma = (target >= start && target < end);
+		}
+		else if (in_target_vma)
+		{
+			if (rss_kb == -1)
+				sscanf(line, "Rss: %ld kB", &rss_kb);
+			if (swap_kb == -1)
+				sscanf(line, "Swap: %ld kB", &swap_kb);
+			if (rss_kb >= 0 && swap_kb >= 0)
+				break;
+		}
+	}
+
+	fclose(f);
+
+	result = rss_kb >= 0 ? mul_size(rss_kb, 1024) : 0;
+	result = add_size(result, swap_kb >= 0 ? mul_size(swap_kb, 1024) : 0);
+
+	PG_RETURN_INT64(result);
+}
+
+/*
+ * 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..24bea91a401
--- /dev/null
+++ b/src/test/modules/resizable_shmem/t/001_resizable_shmem.pl
@@ -0,0 +1,241 @@
+# 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 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_shared_memory;') 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 <> minimum_size;"),
+							'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');
+
+###
+# 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.
+###
+my $resizable_shmem_binary = $node->safe_psql('postgres', 'SHOW have_resizable_shared_memory;') eq 'on';
+if ($resizable_shmem_binary)
+{
+	###
+	# 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;
+
+	is($node->safe_psql('postgres', 'SHOW have_resizable_shared_memory'), 'off',
+						'have_resizable_shared_memory is off with sysv');
+
+	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..92a8f3b4873 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,36 @@ 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_shared_memory;') 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");
+}
+
+###
+# 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/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..0dd469891ee 100644
--- a/src/test/modules/test_shmem/test_shmem.c
+++ b/src/test/modules/test_shmem/test_shmem.c
@@ -99,3 +99,17 @@ 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)
+{
+	int32		new_size = PG_GETARG_INT32(0);
+
+	ShmemResizeStruct("test_shmem area", new_size);
+	PG_RETURN_VOID();
+}
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index a65a5bf0c4f..a882d799133 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1770,8 +1770,11 @@ 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,
+    minimum_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
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 9e6a39f5608..c5a84a2ae17 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3153,6 +3153,7 @@ TestDSMRegistryHashEntry
 TestDSMRegistryStruct
 TestDecodingData
 TestDecodingTxnData
+TestResizableShmemStruct
 TestShmemData
 TestSpec
 TestValueType

base-commit: b6ccd30d8ff6422ad0f79ce2fc801f2437d90664
-- 
2.34.1

