From 4e9c825f9c351533e2c9ac4ed2f3390eaa4e0d5d Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Date: Mon, 16 Feb 2026 20:00:10 +0530
Subject: [PATCH 3/3] WIP: resizable shared memory structures

---
 doc/src/sgml/system-views.sgml                |  10 +
 src/backend/port/sysv_shmem.c                 | 140 +++++++--
 src/backend/port/win32_shmem.c                |  16 +-
 src/backend/storage/ipc/ipci.c                |   5 +-
 src/backend/storage/ipc/shmem.c               | 101 ++++++-
 src/include/catalog/pg_proc.dat               |   4 +-
 src/include/storage/pg_shmem.h                |   4 +-
 src/include/storage/shmem.h                   |   8 +
 src/test/modules/Makefile                     |   1 +
 src/test/modules/meson.build                  |   1 +
 src/test/modules/resizable_shmem/Makefile     |  23 ++
 .../expected/resizable_shmem.out              | 149 ++++++++++
 src/test/modules/resizable_shmem/meson.build  |  37 +++
 .../resizable_shmem/resizable_shmem--1.0.sql  |  39 +++
 .../modules/resizable_shmem/resizable_shmem.c | 270 ++++++++++++++++++
 .../resizable_shmem/resizable_shmem.conf      |   4 +
 .../resizable_shmem/resizable_shmem.control   |   5 +
 .../specs/resizable_shmem.spec                |  42 +++
 src/test/regress/expected/rules.out           |   5 +-
 19 files changed, 830 insertions(+), 34 deletions(-)
 create mode 100644 src/test/modules/resizable_shmem/Makefile
 create mode 100644 src/test/modules/resizable_shmem/expected/resizable_shmem.out
 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.conf
 create mode 100644 src/test/modules/resizable_shmem/resizable_shmem.control
 create mode 100644 src/test/modules/resizable_shmem/specs/resizable_shmem.spec

diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 8b4abef8c68..1173e99fc56 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -4247,6 +4247,16 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
        the columns will be equal in that case also.
       </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 allocation can grow upto including padding
+       in case of resizable allocations. It is zero otherwise.
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/src/backend/port/sysv_shmem.c b/src/backend/port/sysv_shmem.c
index 2e3886cf9fe..8d7b1a64c92 100644
--- a/src/backend/port/sysv_shmem.c
+++ b/src/backend/port/sysv_shmem.c
@@ -39,7 +39,13 @@
 #include "utils/guc_hooks.h"
 #include "utils/pidfile.h"
 
-
+/*
+ * TODO: third paragraph should mention that we use memfd_create to create
+ * shared memory segment, and possibly there's a way to share that segment
+ * between two processes using the file descriptor instead of going through SysV
+ * shared memory segment. So one day EXEC_BACKEND can also use anonymous shared
+ * memory.
+ */
 /*
  * As of PostgreSQL 9.3, we normally allocate only a very small amount of
  * System V shared memory, and only for the purposes of providing an
@@ -97,6 +103,7 @@ void	   *UsedShmemSegAddr = NULL;
 
 static Size AnonymousShmemSize;
 static void *AnonymousShmem = NULL;
+static int AnonymousShmemFd = -1;
 
 static void *InternalIpcMemoryCreate(IpcMemoryKey memKey, Size size);
 static void IpcMemoryDetach(int status, Datum shmaddr);
@@ -471,19 +478,20 @@ PGSharedMemoryAttach(IpcMemoryId shmId,
  * hugepage sizes, we might want to think about more invasive strategies,
  * such as increasing shared_buffers to absorb the extra space.
  *
- * Returns the (real, assumed or config provided) page size into
- * *hugepagesize, and the hugepage-related mmap flags to use into
- * *mmap_flags if requested by the caller.  If huge pages are not supported,
- * *hugepagesize and *mmap_flags are set to 0.
+ * Returns the (real, assumed or config provided) page size into *hugepagesize,
+ * the hugepage-related mmap and memfd flags to use into *mmap_flags and
+ * *memfd_flags respectively if requested by the caller. If huge pages are not supported,
+ * *hugepagesize, *mmap_flags and *memfd_flags are set to 0.
  */
 void
-GetHugePageSize(Size *hugepagesize, int *mmap_flags)
+GetHugePageSize(Size *hugepagesize, int *mmap_flags, int *memfd_flags)
 {
 #ifdef MAP_HUGETLB
 
 	Size		default_hugepagesize = 0;
 	Size		hugepagesize_local = 0;
 	int			mmap_flags_local = 0;
+	int			memfd_flags_local = 0;
 
 	/*
 	 * System-dependent code to find out the default huge page size.
@@ -542,6 +550,7 @@ GetHugePageSize(Size *hugepagesize, int *mmap_flags)
 	}
 
 	mmap_flags_local = MAP_HUGETLB;
+	memfd_flags_local = MFD_HUGETLB;
 
 	/*
 	 * On recent enough Linux, also include the explicit page size, if
@@ -556,11 +565,22 @@ GetHugePageSize(Size *hugepagesize, int *mmap_flags)
 	}
 #endif
 
+#if defined(MFD_HUGE_MASK) && defined(MFD_HUGE_SHIFT)
+	if (hugepagesize_local != default_hugepagesize)
+	{
+		int			shift = pg_ceil_log2_64(hugepagesize_local);
+
+		memfd_flags_local |= (shift & MFD_HUGE_MASK) << MFD_HUGE_SHIFT;
+	}
+#endif
+
 	/* assign the results found */
 	if (mmap_flags)
 		*mmap_flags = mmap_flags_local;
 	if (hugepagesize)
 		*hugepagesize = hugepagesize_local;
+	if (memfd_flags)
+		*memfd_flags = memfd_flags_local;
 
 #else
 
@@ -568,6 +588,8 @@ GetHugePageSize(Size *hugepagesize, int *mmap_flags)
 		*hugepagesize = 0;
 	if (mmap_flags)
 		*mmap_flags = 0;
+	if (memfd_flags)
+		*memfd_flags = 0;
 
 #endif							/* MAP_HUGETLB */
 }
@@ -597,12 +619,15 @@ check_huge_page_size(int *newval, void **extra, GucSource source)
  * larger than requested.
  */
 static void *
-CreateAnonymousSegment(Size *size)
+CreateAnonymousSegment(Size *size, int *fd)
 {
 	Size		allocsize = *size;
 	void	   *ptr = MAP_FAILED;
 	int			mmap_errno = 0;
-	int			mmap_flags = MAP_SHARED | MAP_ANONYMOUS | MAP_HASSEMAPHORE;
+	/* TODO: When memfd_create is not available we should add MAP_ANONYMOUS */
+	int			mmap_flags = MAP_SHARED | MAP_HASSEMAPHORE;
+	int			memfd_flags = 0;
+	int			anonfd = -1;
 
 #ifndef MAP_HUGETLB
 	/* PGSharedMemoryCreate should have dealt with this case */
@@ -615,18 +640,33 @@ CreateAnonymousSegment(Size *size)
 		 */
 		Size		hugepagesize;
 		int			huge_mmap_flags;
+		int 		huge_memfd_flags;
 
-		GetHugePageSize(&hugepagesize, &huge_mmap_flags);
+		GetHugePageSize(&hugepagesize, &huge_mmap_flags, &huge_memfd_flags);
 
-		if (allocsize % hugepagesize != 0)
-			allocsize = add_size(allocsize, hugepagesize - (allocsize % hugepagesize));
-
-		ptr = mmap(NULL, allocsize, PROT_READ | PROT_WRITE,
-				   mmap_flags | huge_mmap_flags, -1, 0);
-		mmap_errno = errno;
-		if (huge_pages == HUGE_PAGES_TRY && ptr == MAP_FAILED)
-			elog(DEBUG1, "mmap(%zu) with MAP_HUGETLB failed, huge pages disabled: %m",
-				 allocsize);
+		/*
+		 * Create an anonymous shared memory file, which can be used with fallocate
+		 * to release memory from resizable shared memory structures.
+		 *
+		 * TODO: We need a way to detect whether memfd_create is supported by the
+		 * running kernel to decide whether we can support resizable shared memory
+		 * objects or not. Need a configure level switch.
+		 */
+		anonfd = memfd_create("main", memfd_flags | huge_memfd_flags);
+		if (anonfd == -1)
+			elog(DEBUG1, "memfd_create with MFD_HUGETBL failed, huge pages will be disabled: %m");
+		else
+		{
+			if (allocsize % hugepagesize != 0)
+				allocsize = add_size(allocsize, hugepagesize - (allocsize % hugepagesize));
+
+			ptr = mmap(NULL, allocsize, PROT_READ | PROT_WRITE,
+					   mmap_flags | huge_mmap_flags, anonfd, 0);
+			mmap_errno = errno;
+			if (huge_pages == HUGE_PAGES_TRY && ptr == MAP_FAILED)
+				elog(DEBUG1, "mmap(%zu) with MAP_HUGETLB failed, huge pages disabled: %m",
+					 allocsize);
+		}
 	}
 #endif
 
@@ -640,13 +680,26 @@ CreateAnonymousSegment(Size *size)
 
 	if (ptr == MAP_FAILED && huge_pages != HUGE_PAGES_ON)
 	{
+		/*
+		 * Create an anonymous shared memory file, which can be used with fallocate
+		 * to release memory from resizable shared memory structures.
+		 *
+		 * TODO: We need a way to detect whether memfd_create is supported by the
+		 * running kernel to decide whether we can support resizable shared memory
+		 * objects or not. Need a configure level switch.
+		 */
+		anonfd = memfd_create("main", memfd_flags);
+		if (anonfd == -1)
+			ereport(FATAL,
+					(errmsg("could not create anonymous shared memory file: %m")));
+
 		/*
 		 * Use the original size, not the rounded-up value, when falling back
 		 * to non-huge pages.
 		 */
 		allocsize = *size;
 		ptr = mmap(NULL, allocsize, PROT_READ | PROT_WRITE,
-				   mmap_flags, -1, 0);
+				   mmap_flags, anonfd, 0);
 		mmap_errno = errno;
 	}
 
@@ -665,7 +718,36 @@ CreateAnonymousSegment(Size *size)
 						 allocsize) : 0));
 	}
 
+	/*
+	 * Set the size of the anonymous file to the actual size of the mapping, so
+	 * that access to the shared memory will be allowed. This does not allocate
+	 * the memory, which will be allocated as written.
+	 */
+	if (anonfd != -1)
+	{
+		if (ftruncate(anonfd, allocsize) == -1)
+		{
+			int			save_errno = errno;
+
+			close(anonfd);
+			anonfd = -1;
+
+			errno = save_errno;
+			ereport(FATAL,
+					(errmsg("could not truncate anonymous file to size %zu: %m",
+							allocsize),
+					 (save_errno == EFBIG) ?
+					 errhint("This error usually means that PostgreSQL's request "
+							 "for a shared memory segment exceeded maximum file size. "
+							 "To reduce the request size (currently %zu bytes), reduce PostgreSQL's shared "
+							 "memory usage, perhaps by reducing \"shared_buffers\" or "
+							 "\"max_connections\".",
+							 allocsize) : 0));
+		}
+	}
+
 	*size = allocsize;
+	*fd = anonfd;
 	return ptr;
 }
 
@@ -738,7 +820,7 @@ PGSharedMemoryCreate(Size size,
 
 	if (shared_memory_type == SHMEM_TYPE_MMAP)
 	{
-		AnonymousShmem = CreateAnonymousSegment(&size);
+		AnonymousShmem = CreateAnonymousSegment(&size, &AnonymousShmemFd);
 		AnonymousShmemSize = size;
 
 		/* Register on-exit routine to unmap the anonymous segment */
@@ -991,3 +1073,21 @@ PGSharedMemoryDetach(void)
 		AnonymousShmem = NULL;
 	}
 }
+
+/*
+ * Release part of the shared memory of given size starting at given address.
+ *
+ * Only supported on platforms that support anonymous shared memory.
+ */
+void PGSharedMemoryRelease(void *addr, Size size)
+{
+	if (!AnonymousShmem)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("releasing shared memory is not supported with the current \"shared_memory_type\" setting")));
+
+	if (fallocate(AnonymousShmemFd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
+				 (off_t) ((char *) addr - (char *) AnonymousShmem), (off_t) size) == -1)
+		ereport(ERROR,
+				(errmsg("could not release shared memory: %m")));
+}
diff --git a/src/backend/port/win32_shmem.c b/src/backend/port/win32_shmem.c
index 794e4fcb2ad..563450b60c2 100644
--- a/src/backend/port/win32_shmem.c
+++ b/src/backend/port/win32_shmem.c
@@ -621,18 +621,32 @@ pgwin32_ReserveSharedMemoryRegion(HANDLE hChild)
 	return true;
 }
 
+/*
+ * Release part of the shared memory of given size from given address.
+ *
+ * Not supported on Windows currently.
+ */
+void PGSharedMemoryRelease(void *addr, Size size)
+{
+	ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("releasing part of shared memory is not supported on windows")));
+}
+
 /*
  * This function is provided for consistency with sysv_shmem.c and does not
  * provide any useful information for Windows.  To obtain the large page size,
  * use GetLargePageMinimum() instead.
  */
 void
-GetHugePageSize(Size *hugepagesize, int *mmap_flags)
+GetHugePageSize(Size *hugepagesize, int *mmap_flags, int *memfd_flags)
 {
 	if (hugepagesize)
 		*hugepagesize = 0;
 	if (mmap_flags)
 		*mmap_flags = 0;
+	if (memfd_flags)
+		*memfd_flags = 0;
 }
 
 /*
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 952988645d0..bd526643d8f 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -360,6 +360,9 @@ InitializeShmemGUCs(void)
 	/*
 	 * Calculate the shared memory size and round up to the nearest megabyte.
 	 */
+	/*
+	 * TODO: we need to do a better job of separating out maximum shared memory
+	 * and minimum shared memory because of on-demand shared memory segments. */
 	size_b = CalculateShmemSize();
 	size_mb = add_size(size_b, (1024 * 1024) - 1) / (1024 * 1024);
 	sprintf(buf, "%zu", size_mb);
@@ -369,7 +372,7 @@ InitializeShmemGUCs(void)
 	/*
 	 * Calculate the number of huge pages required.
 	 */
-	GetHugePageSize(&hp_size, NULL);
+	GetHugePageSize(&hp_size, NULL, NULL);
 	if (hp_size != 0)
 	{
 		Size		hp_required;
diff --git a/src/backend/storage/ipc/shmem.c b/src/backend/storage/ipc/shmem.c
index e73ac489b2b..1e19c30ef58 100644
--- a/src/backend/storage/ipc/shmem.c
+++ b/src/backend/storage/ipc/shmem.c
@@ -143,6 +143,9 @@ ShmemRegisterStruct(ShmemStructDesc *desc)
 	elog(DEBUG2, "REGISTER: %s with size %zd", desc->name, desc->size);
 
 	registry[num_registrations++] = desc;
+
+	if (desc->max_size > 0)
+		elog(DEBUG2, "RESIZABLE structure: %s has max_size %zd", desc->name, desc->max_size);
 }
 
 size_t
@@ -153,7 +156,7 @@ ShmemRegisteredSize(void)
 	size = 0;
 	for (int i = 0; i < num_registrations; i++)
 	{
-		size = add_size(size, registry[i]->size);
+		size = add_size(size, registry[i]->max_size > 0 ? registry[i]->max_size : registry[i]->size);
 		size = add_size(size, registry[i]->extra_size);
 	}
 
@@ -190,8 +193,17 @@ ShmemInitRegistered(void)
 		if (found)
 			elog(ERROR, "shmem struct \"%s\" is already initialized", registry[i]->name);
 
-		/* allocate and initialize it */
-		structPtr = ShmemAllocRaw(registry[i]->size, &allocated_size);
+		/*
+		 * allocate and initialize the structures.
+		 *
+		 * Allocate maximum size for resizable structures. This should not
+		 * allocate memory but just the address space. Memory will be allocated
+		 * as the address space is written to. It is expected that the
+		 * registrants do not use memory beyond the initial size until they have
+		 * resized the structure. If they do so, this will result in allocating
+		 * more memory than expected.
+		 */
+		structPtr = ShmemAllocRaw(registry[i]->max_size > 0 ? registry[i]->max_size : registry[i]->size, &allocated_size);
 		if (structPtr == NULL)
 		{
 			/* out of memory; remove the failed ShmemIndex entry */
@@ -203,7 +215,8 @@ ShmemInitRegistered(void)
 							registry[i]->name, registry[i]->size)));
 		}
 		result->size = registry[i]->size;
-		result->allocated_size = allocated_size;
+		result->allocated_size = registry[i]->max_size > 0 ? registry[i]->size : allocated_size;
+		result->reservedsize = allocated_size;
 		result->location = structPtr;
 
 		*(registry[i]->ptr) = structPtr;
@@ -212,6 +225,54 @@ ShmemInitRegistered(void)
 	}
 }
 
+#ifndef EXEC_BACKEND
+void
+ShmemResizeRegistered(const char *name, Size new_size)
+{
+	ShmemIndexEnt *result;
+	bool found;
+	/* Cachealign new size */
+	Size allocated_size = CACHELINEALIGN(new_size);
+
+	/* look it up in the shmem index */
+	LWLockAcquire(ShmemIndexLock, LW_EXCLUSIVE);
+	result = (ShmemIndexEnt *)
+		hash_search(ShmemIndex, name, HASH_FIND, &found);
+	if (!found)
+		elog(ERROR, "shmem struct \"%s\" is not initialized", name);
+
+	Assert(result);
+
+	if (result->reservedsize < allocated_size)
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_RESOURCES),
+				 errmsg("not enough address space is reserved for resizing structure \"%s\"", name)));
+
+	if (allocated_size == result->allocated_size)
+	{
+		result->size = new_size;
+		/* No need to resize if the existing allocated size is sufficient */
+		LWLockRelease(ShmemIndexLock);
+		return;
+	}
+
+	/*
+	 * Resize: If shrinking release memory which is not required anymore. If
+	 * expanding we don't have do anything as the memory is allocated when it's
+	 * written to.
+	 */
+	if (allocated_size < result->allocated_size)
+		PGSharedMemoryRelease((char *) result->location + allocated_size,
+								result->allocated_size - allocated_size);
+
+	/* Update shmem index entry. */
+	result->size = new_size;
+	result->allocated_size = allocated_size;
+
+	LWLockRelease(ShmemIndexLock);
+}
+#endif
+
 #ifdef EXEC_BACKEND
 void
 ShmemAttachRegistered(void)
@@ -700,6 +761,7 @@ ShmemInitStruct(const char *name, Size size, bool *foundPtr)
 		}
 		result->size = size;
 		result->allocated_size = allocated_size;
+		result->reservedsize = allocated_size;
 		result->location = structPtr;
 	}
 
@@ -743,11 +805,26 @@ mul_size(Size s1, Size s2)
 	return result;
 }
 
+/*
+ * Round up the given size to the next multiple of the given alignment, checking
+ * for overflow.
+ */
+Size
+align_size(Size size, Size alignment)
+{
+
+	Assert(alignment != 0);
+
+	if (size % alignment == 0)
+		return size;
+	return add_size(size, alignment - (size % alignment));
+}
+
 /* SQL SRF showing allocated shared memory */
 Datum
 pg_get_shmem_allocations(PG_FUNCTION_ARGS)
 {
-#define PG_GET_SHMEM_SIZES_COLS 4
+#define PG_GET_SHMEM_SIZES_COLS 5
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	HASH_SEQ_STATUS hstat;
 	ShmemIndexEnt *ent;
@@ -769,7 +846,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->reservedsize);
+
+		/*
+		 * The shared memory segment metadata does not know about the internal
+		 * reservation. From it's point of view the maximum size of a resizable
+		 * shared structure is considered to be allocated. In order to compute
+		 * the amount of memory allocated to the unnamed structures, add
+		 * reserved size, which is same as the allocated size for a fixed sized
+		 * structure.
+		 */
+		named_allocated += ent->reservedsize;
 
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
 							 values, nulls);
@@ -989,7 +1076,7 @@ pg_get_shmem_pagesize(void)
 	Assert(huge_pages_status != HUGE_PAGES_UNKNOWN);
 
 	if (huge_pages_status == HUGE_PAGES_ON)
-		GetHugePageSize(&os_page_size, NULL);
+		GetHugePageSize(&os_page_size, NULL, NULL);
 
 	return os_page_size;
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 83f6501df38..dfd3389ced3 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8592,8 +8592,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}', proargmodes => '{o,o,o,o,o}',
+  proargnames => '{name,off,size,allocated_size,reserved_size}',
   prosrc => 'pg_get_shmem_allocations' },
 
 { oid => '4099', descr => 'Is NUMA support available?',
diff --git a/src/include/storage/pg_shmem.h b/src/include/storage/pg_shmem.h
index 10c7b065861..10bf2505a4a 100644
--- a/src/include/storage/pg_shmem.h
+++ b/src/include/storage/pg_shmem.h
@@ -89,6 +89,8 @@ extern PGShmemHeader *PGSharedMemoryCreate(Size size,
 										   PGShmemHeader **shim);
 extern bool PGSharedMemoryIsInUse(unsigned long id1, unsigned long id2);
 extern void PGSharedMemoryDetach(void);
-extern void GetHugePageSize(Size *hugepagesize, int *mmap_flags);
+extern void PGSharedMemoryRelease(void *addr, Size size);
+extern void GetHugePageSize(Size *hugepagesize, int *mmap_flags,
+							int *memfd_flags);
 
 #endif							/* PG_SHMEM_H */
diff --git a/src/include/storage/shmem.h b/src/include/storage/shmem.h
index cbd4ef8d03f..68485c61167 100644
--- a/src/include/storage/shmem.h
+++ b/src/include/storage/shmem.h
@@ -50,6 +50,9 @@ typedef struct ShmemStructDesc
 	 */
 	size_t		extra_size;
 
+	/* Maximum size this structure can grow upto in future. The memory is not allocated right away but the corresponding address space is allocated so that memory can be mapped to it when the structure grows. Typically should be used for structures like buffer blocks which need contiguous and large memory. */
+	size_t		max_size;
+
 	/* Pointer to the variable to which pointer to this shared memory area is assigned after allocation. */
 	void	   **ptr;
 } ShmemStructDesc;
@@ -84,6 +87,9 @@ extern void InitShmemIndex(void);
 
 extern void ShmemRegisterHash(ShmemHashDesc *desc, HASHCTL *infoP, int hash_flags);
 extern void ShmemRegisterStruct(ShmemStructDesc *desc);
+#ifndef EXEC_BACKEND
+extern void ShmemResizeRegistered(const char *name, Size new_size);
+#endif
 
 /* Legacy functions */
 extern HTAB *ShmemInitHash(const char *name, int64 init_size, int64 max_size,
@@ -96,6 +102,7 @@ extern void ShmemAttachRegistered(void);
 
 extern Size add_size(Size s1, Size s2);
 extern Size mul_size(Size s1, Size s2);
+extern Size align_size(Size size, Size align);
 
 extern PGDLLIMPORT Size pg_get_shmem_pagesize(void);
 
@@ -115,6 +122,7 @@ typedef struct
 	void	   *location;		/* location in shared mem */
 	Size		size;			/* # bytes requested for the structure */
 	Size		allocated_size; /* # bytes actually allocated */
+	Size		reservedsize;   /* # bytes reserved for this resizable structure. */
 } ShmemIndexEnt;
 
 #endif							/* SHMEM_H */
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 44c7163c1cd..a5df6edae18 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 2634a519935..961bb62759d 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..ad2f040b2f0
--- /dev/null
+++ b/src/test/modules/resizable_shmem/Makefile
@@ -0,0 +1,23 @@
+# src/test/modules/resizable_shmem/Makefile
+
+MODULES = resizable_shmem
+ISOLATION = resizable_shmem
+
+EXTENSION = resizable_shmem
+DATA = resizable_shmem--1.0.sql
+PGFILEDESC = "resizable_shmem - test module for resizable shared memory"
+
+# 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)/src/makefiles/pgxs.mk
+endif
diff --git a/src/test/modules/resizable_shmem/expected/resizable_shmem.out b/src/test/modules/resizable_shmem/expected/resizable_shmem.out
new file mode 100644
index 00000000000..60be53c0cfe
--- /dev/null
+++ b/src/test/modules/resizable_shmem/expected/resizable_shmem.out
@@ -0,0 +1,149 @@
+Parsed test spec with 3 sessions
+
+starting permutation: s1u s2u zu zia s1w_initial s1u s2r_initial s2u zs_max zu zia s2r_initial s2w_max s2u s1r_max s1u zs_shrink zu zia s1r_shrink s1w_shrink s1u s2r_shrink s2u zs_fail
+step s1u: SELECT pg_size_pretty(rss_anon) AS rss_anon, pg_size_pretty(rss_file) AS rss_file, pg_size_pretty(rss_shmem) AS rss_shmem, pg_size_pretty(vm_size) AS vm_size FROM resizable_shmem_usage() AS "session1_shmem_usage";
+rss_anon|rss_file|rss_shmem|vm_size
+--------+--------+---------+-------
+1828 kB |10 MB   |5248 kB  |1173 MB
+(1 row)
+
+step s2u: SELECT pg_size_pretty(rss_anon) AS rss_anon, pg_size_pretty(rss_file) AS rss_file, pg_size_pretty(rss_shmem) AS rss_shmem, pg_size_pretty(vm_size) AS vm_size FROM resizable_shmem_usage() AS "session2_shmem_usage";
+rss_anon|rss_file|rss_shmem|vm_size
+--------+--------+---------+-------
+1828 kB |10 MB   |5120 kB  |1173 MB
+(1 row)
+
+step zu: SELECT pg_size_pretty(rss_anon) AS rss_anon, pg_size_pretty(rss_file) AS rss_file, pg_size_pretty(rss_shmem) AS rss_shmem, pg_size_pretty(vm_size) AS vm_size FROM resizable_shmem_usage() AS "resizer_shmem_usage";
+rss_anon|rss_file|rss_shmem|vm_size
+--------+--------+---------+-------
+1684 kB |10 MB   |5248 kB  |1173 MB
+(1 row)
+
+step zia: SELECT name, pg_size_pretty(size) AS size, pg_size_pretty(allocated_size) AS allocated_size, pg_size_pretty(reserved_size) AS reserved_size FROM pg_shmem_allocations WHERE name = 'resizable_shmem';
+name           |size  |allocated_size|reserved_size
+---------------+------+--------------+-------------
+resizable_shmem|100 MB|100 MB        |400 MB       
+(1 row)
+
+step s1w_initial: SELECT resizable_shmem_write(100);
+resizable_shmem_write
+---------------------
+                     
+(1 row)
+
+step s1u: SELECT pg_size_pretty(rss_anon) AS rss_anon, pg_size_pretty(rss_file) AS rss_file, pg_size_pretty(rss_shmem) AS rss_shmem, pg_size_pretty(vm_size) AS vm_size FROM resizable_shmem_usage() AS "session1_shmem_usage";
+rss_anon|rss_file|rss_shmem|vm_size
+--------+--------+---------+-------
+1828 kB |10 MB   |105 MB   |1173 MB
+(1 row)
+
+step s2r_initial: SELECT resizable_shmem_read(25 * 1024 * 1024, 100);
+resizable_shmem_read
+--------------------
+t                   
+(1 row)
+
+step s2u: SELECT pg_size_pretty(rss_anon) AS rss_anon, pg_size_pretty(rss_file) AS rss_file, pg_size_pretty(rss_shmem) AS rss_shmem, pg_size_pretty(vm_size) AS vm_size FROM resizable_shmem_usage() AS "session2_shmem_usage";
+rss_anon|rss_file|rss_shmem|vm_size
+--------+--------+---------+-------
+1828 kB |10 MB   |105 MB   |1173 MB
+(1 row)
+
+step zs_max: SELECT resizable_shmem_resize(100 * 1024 * 1024);
+resizable_shmem_resize
+----------------------
+                      
+(1 row)
+
+step zu: SELECT pg_size_pretty(rss_anon) AS rss_anon, pg_size_pretty(rss_file) AS rss_file, pg_size_pretty(rss_shmem) AS rss_shmem, pg_size_pretty(vm_size) AS vm_size FROM resizable_shmem_usage() AS "resizer_shmem_usage";
+rss_anon|rss_file|rss_shmem|vm_size
+--------+--------+---------+-------
+1812 kB |10 MB   |5888 kB  |1173 MB
+(1 row)
+
+step zia: SELECT name, pg_size_pretty(size) AS size, pg_size_pretty(allocated_size) AS allocated_size, pg_size_pretty(reserved_size) AS reserved_size FROM pg_shmem_allocations WHERE name = 'resizable_shmem';
+name           |size  |allocated_size|reserved_size
+---------------+------+--------------+-------------
+resizable_shmem|400 MB|400 MB        |400 MB       
+(1 row)
+
+step s2r_initial: SELECT resizable_shmem_read(25 * 1024 * 1024, 100);
+resizable_shmem_read
+--------------------
+t                   
+(1 row)
+
+step s2w_max: SELECT resizable_shmem_write(500);
+resizable_shmem_write
+---------------------
+                     
+(1 row)
+
+step s2u: SELECT pg_size_pretty(rss_anon) AS rss_anon, pg_size_pretty(rss_file) AS rss_file, pg_size_pretty(rss_shmem) AS rss_shmem, pg_size_pretty(vm_size) AS vm_size FROM resizable_shmem_usage() AS "session2_shmem_usage";
+rss_anon|rss_file|rss_shmem|vm_size
+--------+--------+---------+-------
+1828 kB |10 MB   |405 MB   |1173 MB
+(1 row)
+
+step s1r_max: SELECT resizable_shmem_read(100 * 1024 * 1024, 500);
+resizable_shmem_read
+--------------------
+t                   
+(1 row)
+
+step s1u: SELECT pg_size_pretty(rss_anon) AS rss_anon, pg_size_pretty(rss_file) AS rss_file, pg_size_pretty(rss_shmem) AS rss_shmem, pg_size_pretty(vm_size) AS vm_size FROM resizable_shmem_usage() AS "session1_shmem_usage";
+rss_anon|rss_file|rss_shmem|vm_size
+--------+--------+---------+-------
+1828 kB |10 MB   |405 MB   |1173 MB
+(1 row)
+
+step zs_shrink: SELECT resizable_shmem_resize(75 * 1024 * 1024);
+resizable_shmem_resize
+----------------------
+                      
+(1 row)
+
+step zu: SELECT pg_size_pretty(rss_anon) AS rss_anon, pg_size_pretty(rss_file) AS rss_file, pg_size_pretty(rss_shmem) AS rss_shmem, pg_size_pretty(vm_size) AS vm_size FROM resizable_shmem_usage() AS "resizer_shmem_usage";
+rss_anon|rss_file|rss_shmem|vm_size
+--------+--------+---------+-------
+1812 kB |10 MB   |5888 kB  |1173 MB
+(1 row)
+
+step zia: SELECT name, pg_size_pretty(size) AS size, pg_size_pretty(allocated_size) AS allocated_size, pg_size_pretty(reserved_size) AS reserved_size FROM pg_shmem_allocations WHERE name = 'resizable_shmem';
+name           |size  |allocated_size|reserved_size
+---------------+------+--------------+-------------
+resizable_shmem|300 MB|300 MB        |400 MB       
+(1 row)
+
+step s1r_shrink: SELECT resizable_shmem_read(75 * 1024 * 1024, 500);
+resizable_shmem_read
+--------------------
+t                   
+(1 row)
+
+step s1w_shrink: SELECT resizable_shmem_write(999);
+resizable_shmem_write
+---------------------
+                     
+(1 row)
+
+step s1u: SELECT pg_size_pretty(rss_anon) AS rss_anon, pg_size_pretty(rss_file) AS rss_file, pg_size_pretty(rss_shmem) AS rss_shmem, pg_size_pretty(vm_size) AS vm_size FROM resizable_shmem_usage() AS "session1_shmem_usage";
+rss_anon|rss_file|rss_shmem|vm_size
+--------+--------+---------+-------
+1828 kB |10 MB   |305 MB   |1173 MB
+(1 row)
+
+step s2r_shrink: SELECT resizable_shmem_read(75 * 1024 * 1024, 999);
+resizable_shmem_read
+--------------------
+t                   
+(1 row)
+
+step s2u: SELECT pg_size_pretty(rss_anon) AS rss_anon, pg_size_pretty(rss_file) AS rss_file, pg_size_pretty(rss_shmem) AS rss_shmem, pg_size_pretty(vm_size) AS vm_size FROM resizable_shmem_usage() AS "session2_shmem_usage";
+rss_anon|rss_file|rss_shmem|vm_size
+--------+--------+---------+-------
+1828 kB |10 MB   |305 MB   |1173 MB
+(1 row)
+
+step zs_fail: SELECT resizable_shmem_resize(128 * 1024 * 1024);
+ERROR:  not enough address space is reserved for resizing structure "resizable_shmem"
diff --git a/src/test/modules/resizable_shmem/meson.build b/src/test/modules/resizable_shmem/meson.build
new file mode 100644
index 00000000000..6e5f6d5caaf
--- /dev/null
+++ b/src/test/modules/resizable_shmem/meson.build
@@ -0,0 +1,37 @@
+# src/test/modules/test_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(),
+  'isolation': {
+    'specs': [
+      'resizable_shmem',
+    ],
+    'regress_args': ['--temp-config', files('resizable_shmem.conf')],
+    # 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..2a5fa957167
--- /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 usage statistics of the calling backend
+--
+-- TODO: This function is useful to determine that the memory is freed after
+-- shrinking and that we don't allocate all the memory upfront instead of just
+-- reserving the address space. the C implementation of this function relies
+-- heavily on Linux-specific /proc/self/stats and also the sizes that it returns
+-- may not be stable across different machines. Hence we should consider
+-- removing this function and the related tests after we have verified the
+-- resizing works as expected.
+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;
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..55c86b8e1ab
--- /dev/null
+++ b/src/test/modules/resizable_shmem/resizable_shmem.c
@@ -0,0 +1,270 @@
+/* -------------------------------------------------------------------------
+ *
+ * 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 "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 amount of shared buffers and hence the amount of shared memory
+ * allocated by default is in hundreds of MBs. The memory allocated to the test
+ * structure will be noticeable only when it's in the same order.
+ */
+#define TEST_INITIAL_ENTRIES	(25 * 1024 * 1024)		/* Initial number of entries (100MB) */
+#define TEST_MAX_ENTRIES		(100 * 1024 * 1024)		/* Maximum number of entries (400MB, 4x initial) */
+#define TEST_ENTRY_SIZE			sizeof(int32)		/* Size of each entry */
+
+/*
+ * Resizable test data structure stored in shared memory.
+ *
+ * We do not use any locks. The test performs resizing, reads and writes none of
+ * which are concurrent to keep the code and the test simple.
+ */
+typedef struct TestResizableShmemStruct
+{
+	/* Metadata */
+	int32		num_entries;		/* Number of entries that can fit */
+
+	/* Data area - variable size */
+	int32		data[FLEXIBLE_ARRAY_MEMBER];
+} TestResizableShmemStruct;
+
+/* Global pointer to our shared memory structure */
+static TestResizableShmemStruct *resizable_shmem = NULL;
+
+static void resizable_shmem_shmem_init(void *arg);
+
+static ShmemStructDesc testShmemDesc = {
+	.name = "resizable_shmem",
+	.size = offsetof(TestResizableShmemStruct, data) + (TEST_INITIAL_ENTRIES * TEST_ENTRY_SIZE),
+	.max_size = offsetof(TestResizableShmemStruct, data) + (TEST_MAX_ENTRIES * TEST_ENTRY_SIZE),
+	.alignment = MAXIMUM_ALIGNOF,
+	.init_fn = resizable_shmem_shmem_init,
+	.ptr = (void **) &resizable_shmem,
+};
+
+static shmem_request_hook_type prev_shmem_request_hook = NULL;
+
+static void resizable_shmem_request(void);
+
+/* 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);
+
+/*
+ * Module load callback
+ */
+void
+_PG_init(void)
+{
+	/*
+	 * The module needs to be loaded via shared_preload_libraries to register
+	 * shared memory structure. But if that's not the case, don't throw an error.
+	 * The SQL functions check for existence of the shared memory data structure.
+	 */
+	if (!process_shared_preload_libraries_in_progress)
+		return;
+
+#ifdef EXEC_BACKEND
+	ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("resizable_shmem is not supported in EXEC_BACKEND builds")));
+#endif
+
+	/* Install hook to register shared memory structure. */
+	prev_shmem_request_hook = shmem_request_hook;
+	shmem_request_hook = resizable_shmem_request;
+}
+
+/*
+ * Request shared memory resources
+ */
+static void
+resizable_shmem_request(void)
+{
+	if (prev_shmem_request_hook)
+		prev_shmem_request_hook();
+
+	/* Register our resizable shared memory structure */
+	ShmemRegisterStruct(&testShmemDesc);
+}
+
+/*
+ * Initialize shared memory structure
+ */
+static void
+resizable_shmem_shmem_init(void *arg)
+{
+	/*
+	 * Shared memory structure should have been allocated with the requested
+	 * size. Initialize the metadata.
+	 */
+	Assert(resizable_shmem != NULL);
+	resizable_shmem->num_entries = TEST_INITIAL_ENTRIES;
+}
+
+/*
+ * Resize the shared memory structure to accommodate the specified number of
+ * entries.
+ */
+Datum
+resizable_shmem_resize(PG_FUNCTION_ARGS)
+{
+#ifndef EXEC_BACKEND
+	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 = offsetof(TestResizableShmemStruct, data) + (new_entries * TEST_ENTRY_SIZE);
+	ShmemResizeRegistered(testShmemDesc.name, new_size);
+	resizable_shmem->num_entries = new_entries;
+
+	PG_RETURN_VOID();
+#else
+	ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("resizing shared memory is not supported in EXEC_BACKEND builds")));
+#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")));
+
+	/* Validate entry_count */
+	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)));
+
+	/* Check if first entry_count entries have the expected value */
+	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.
+ *
+ * TODO: See TODO note in SQL definition of this function.
+ */
+Datum
+resizable_shmem_usage(PG_FUNCTION_ARGS)
+{
+	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));
+}
diff --git a/src/test/modules/resizable_shmem/resizable_shmem.conf b/src/test/modules/resizable_shmem/resizable_shmem.conf
new file mode 100644
index 00000000000..764b6357cbb
--- /dev/null
+++ b/src/test/modules/resizable_shmem/resizable_shmem.conf
@@ -0,0 +1,4 @@
+shared_preload_libraries = 'resizable_shmem'
+# Turn huge pages off so that we can test changes in the shared memory smaller
+# than huge page size which may be in GBs.
+huge_pages = off
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..1ce2c5ea21a
--- /dev/null
+++ b/src/test/modules/resizable_shmem/resizable_shmem.control
@@ -0,0 +1,5 @@
+# resizable_shmem extension test module
+comment = 'test module for testing resizable shared memory structure functionality'
+default_version = '1.0'
+module_pathname = '$libdir/resizable_shmem'
+relocatable = true
diff --git a/src/test/modules/resizable_shmem/specs/resizable_shmem.spec b/src/test/modules/resizable_shmem/specs/resizable_shmem.spec
new file mode 100644
index 00000000000..88ba022fe04
--- /dev/null
+++ b/src/test/modules/resizable_shmem/specs/resizable_shmem.spec
@@ -0,0 +1,42 @@
+# Test resizable shared memory structure
+#
+# It tests that a resizable shared memory structure can be resized from any
+# backend and that the new sizes are visible to all the backends. It uses
+# isolation test infrastructure so that resizing, reading and writing can be
+# interleaved.
+
+setup
+{
+  CREATE EXTENSION resizable_shmem;
+}
+
+teardown
+{
+  DROP EXTENSION resizable_shmem;
+}
+
+session "session1"
+step s1w_initial { SELECT resizable_shmem_write(100); }
+step s1r_max { SELECT resizable_shmem_read(100 * 1024 * 1024, 500); }
+step s1r_shrink { SELECT resizable_shmem_read(75 * 1024 * 1024, 500); }
+step s1w_shrink { SELECT resizable_shmem_write(999); }
+step s1u { SELECT pg_size_pretty(rss_anon) AS rss_anon, pg_size_pretty(rss_file) AS rss_file, pg_size_pretty(rss_shmem) AS rss_shmem, pg_size_pretty(vm_size) AS vm_size FROM resizable_shmem_usage() AS "session1_shmem_usage"; }
+
+session "session2"
+step s2r_initial { SELECT resizable_shmem_read(25 * 1024 * 1024, 100); }
+step s2w_max { SELECT resizable_shmem_write(500); }
+step s2r_shrink { SELECT resizable_shmem_read(75 * 1024 * 1024, 999); }
+step s2u { SELECT pg_size_pretty(rss_anon) AS rss_anon, pg_size_pretty(rss_file) AS rss_file, pg_size_pretty(rss_shmem) AS rss_shmem, pg_size_pretty(vm_size) AS vm_size FROM resizable_shmem_usage() AS "session2_shmem_usage"; }
+
+session "resizer"
+step zs_max { SELECT resizable_shmem_resize(100 * 1024 * 1024); }
+step zs_shrink { SELECT resizable_shmem_resize(75 * 1024 * 1024); }
+step zs_fail { SELECT resizable_shmem_resize(128 * 1024 * 1024); } # this fails (512MB > 400MB max)
+step zia { SELECT name, pg_size_pretty(size) AS size, pg_size_pretty(allocated_size) AS allocated_size, pg_size_pretty(reserved_size) AS reserved_size FROM pg_shmem_allocations WHERE name = 'resizable_shmem'; }
+step zu { SELECT pg_size_pretty(rss_anon) AS rss_anon, pg_size_pretty(rss_file) AS rss_file, pg_size_pretty(rss_shmem) AS rss_shmem, pg_size_pretty(vm_size) AS vm_size FROM resizable_shmem_usage() AS "resizer_shmem_usage"; }
+
+# Test shrinking and expanding the shared memory, while ensuring that:
+# - All entries can be written to and read from after each resize
+# - Previously written data remains unchanged in entries that survive resizing
+# Now using 100MB initial (25M entries), 400MB max (100M entries), 300MB shrink (75M entries)
+permutation s1u s2u zu zia s1w_initial s1u s2r_initial s2u zs_max zu zia s2r_initial s2w_max s2u s1r_max s1u zs_shrink zu zia s1r_shrink s1w_shrink s1u s2r_shrink s2u zs_fail
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index f4ee2bd7459..a6eaeb49dbc 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1770,8 +1770,9 @@ 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,
+    reserved_size
+   FROM pg_get_shmem_allocations() pg_get_shmem_allocations(name, off, size, allocated_size, reserved_size);
 pg_shmem_allocations_numa| SELECT name,
     numa_node,
     size
-- 
2.34.1

