public inbox for [email protected]  
help / color / mirror / Atom feed
From: Ashutosh Bapat <[email protected]>
To: Heikki Linnakangas <[email protected]>
Cc: Matthias van de Meent <[email protected]>
Cc: Robert Haas <[email protected]>
Cc: Andres Freund <[email protected]>
Cc: pgsql-hackers <[email protected]>
Cc: [email protected]
Subject: Re: Better shared data structure management and resizable shared data structures
Date: Sun, 5 Apr 2026 11:28:51 +0530
Message-ID: <CAExHW5stth2mdXh3ukn9rWJ+Pruoat+5tY3CYyM6KGqH2G30fQ@mail.gmail.com> (raw)
In-Reply-To: <CAExHW5twYLhVjtApX3osdaz-4b0Tjvjj_Zr20orUb61qgiJmUw@mail.gmail.com>
References: <CAExHW5vM1bneLYfg0wGeAa=52UiJ3z4vKd3AJ72X8Fw6k3KKrg@mail.gmail.com>
	<[email protected]>
	<CA+TgmoauqYXm8iA3FGRAVKxYShUxWBiS_MSLmQpTrmO7wNHamw@mail.gmail.com>
	<[email protected]>
	<CAExHW5uTNWOSxJDWQAUnS0tZawob2_J3dRAtc67NHNZ98X4_xA@mail.gmail.com>
	<CAExHW5t439y61YD9bc7d5wZWHp6J=M43Qu3eEZOBPguZML7o2A@mail.gmail.com>
	<CAExHW5v5FVZbsO9sLzztMZ11C3hgGStE=HkkV2bQkCyncess4w@mail.gmail.com>
	<[email protected]>
	<CAExHW5tCC0T1ky=Jnq-AvMxa67Adaw7aQ4iQAO=BSdHcbSNBVg@mail.gmail.com>
	<[email protected]>
	<CAExHW5tS7GncN90oJWOSzW_3F1EHL9xwe59L7Req3nUVgmObUw@mail.gmail.com>
	<[email protected]>
	<CAEze2WhMOHVgH2Xeyzx=VEk-Ta_YnQUqT+TdBiv5Lx8ESn2WZA@mail.gmail.com>
	<CAExHW5s6h=c_q2m72Nvyj1ghMEhPkOBkeN5Htn7YR=1BrNN-Sw@mail.gmail.com>
	<[email protected]>
	<CAEze2WjQZff3znd6CtG-OBzYZMMqy5TyQSoAo=QTFT38tDndeQ@mail.gmail.com>
	<[email protected]>
	<CAExHW5twYLhVjtApX3osdaz-4b0Tjvjj_Zr20orUb61qgiJmUw@mail.gmail.com>

On Sun, Apr 5, 2026 at 11:18 AM Ashutosh Bapat
<[email protected]> wrote:
>
> I will post my resizable shmem structures patch in a separate email in
> this thread but continue to review your patches.
>

Attached is your patchset (0001 - 0014) + resizable shared memory
structures patchset 0015.

Resizable shared memory structures
============================

When allocating memory to the requested shared structures, we allocate
space for each structure. In mmap'ed shared memory, the memory is
allocated against those structures only when those structures are
initialized.
Resizable shared memory structures are simply allocated maximum space
when that happens. The function which initializes the structure is
expected to initialize only the memory worth its initial size. When
resizing the structure memory is freed or allocated against the
reserved space depending upon the new size. This allows the structures
to be resized while keeping their starting address stable which is a
hard requirement in PostgreSQL.

Resizable shared memory feature depends upon the 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 the existence of madvise
separately, since the existence of the constants implies the 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 isn't going to
impact PostgreSQL users much.

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 a running server supports resizable structures, we
have introduced GUC have_resizable_shmem.

Following points are up for discussion
=============================

1. calculation of allocated_size of resizable structures
--------------------------------
For fixed sized shared memory structures, allocated_size is the size
of the aligned structure. Assuming that the whole structure is
initialized, it is also the memory allocated to the structure. Thus
summing all allocated_size's of the allocations gives a
nearly-accurate (considering page sized allocations) idea of the total
shared memory allocated. For a resizable structure, it's a bit more
complicated. We allocate maximum space required by the structure at
the beginning. At a given point in time, the memory page where the
next structure begins and the page which contains the end of the
structure at that point in time are allocated. The pages in-between
are not allocated. The memory allocated to that structure is the
{maximum size of the structure} - {total size of unallocated pages}. I
think setting allocated_size to the actually allocated memory is more
accurate than {current size of the structure} + {alignment} which does
not reflect the actual memory allocated to the structure. I would like
to know what others think.

2. maximum_size member in various structures and in pg_shmem_allocations view
-----------------------------------------------------------------------------
A resizable structure is requested by specifying non-zero maximum_size
in ShmemStructOpts. It gets copied to the maximum_size member in
ShmemStructDesc, ShmemIndexEnt. The question is for fixed-size
structures what should be the value maximum_size in those structures.
Setting it to the same value as the size member in the respective
structure is logical since their maximum size is the same as their
initial size. But if we do so, we need another member in
ShmemStructDesc and ShmemIndexEnt to indicate whether the structure is
resizable or not. Instead the patches set maximum_size to 0 for
fixed-size structures and non-zero for resizable structures. This way
we can check whether a structure is resizable or not by checking
whether its maximum_size is zero or not. pg_shmem_allocations view
also has a maximum_size column which has the similar characteristics.
I would like to know what others think.

3. allocated_space member in various structures and in pg_shmem_allocations view
-------------------------------------------------------------------------------
The patch adds a new member allocated_space to ShmemIndexEnt and
pg_shmem_allocations view. allocated_space to maximum_size is what
allocated_size is to size - it's the type aligned value of
maximum_size. But it also highlights the difference between the
address space allocation and the actual memory allocation. This
difference is crucial to resizable structures. However, unlike
maximum_size, we set it to a non-zero value, allocated_size, for
fixed-size structures as well since they are allocated the same amount
of space as their allocated_size. While this seems logically correct
to me, some may find maximum_size to be zero but allocated_space to be
non-zero for fixed-size structures a bit weird. I would like to know
what others think.

As a minor point, setting allocated_space to allocated_size makes the
calculations in pg_shmem_allocations() a bit easier. However, that can
be fixed trivially.

As a side question, do we want to allow users to specify minimum_size
in ShmemStructOpts for resizable structures? Resizing memory lower
than that would be prohibited. For fixed sized structures,
minimum_size would be same as size and also maximum_size. For now, it
seems only for the sanity checks, but it could be seen as a useful
safety feature. A difference in maximum_size and minimum_size would
indicate that the structure is resizable.

Considering 2 and 3 together, we have the following options
a. As implemented in patch and clarified in documentation.
b. Set maximum_size to size and allocated_space to allocated_size for
fixed-size structures, but add a new member to indicate whether the
structure is resizable or not.
c. Set maximum_size and allocated_space to zero for fixed-size
structures and explicitly mention it in the documentation.

4. to mprotect or not to mprotect
---------------------------------
If memory beyond the current size of a resizable structure is
accessed, it won't cause any segfault or bus error. When writing
memory will be simply allocated and when reading, it will return
zeroes if memory is not allocated yet. mprotect'ing the memory beyond
the current size of a resizable structure to PROT_NONE can prevent
accidental access to unallocated memory (sans page boundaries), but it
needs to be done in every backend process which requires a
synchronization mechanism beyond the scope of shmem.c. Hence the patch
does not use mprotect. A subsystem will require some higher level
synchronization mechanism between users of the structure and the
process which resizes it. That synchronization mechanism can be used
to mprotect the memory, if required. I have documented this, but I
would like to know whether we should provide an API in shmem.c to
mprotect.

6. Tests
-------
The patch adds a new test module resizable_shmem which tests the
resizable shared memory feature. Also it adds a test case to the
test_shmem module to make sure that the fixed-size shared memory
structures can not be resized. I think the resizable_shmem module
should be merged into test_shmem. But I have kept these two separate
for ease of review. Please let me know if you also think they should
be merged.

I have self-reviewed the tests a few times, fixing issues and
adjusting the test and module code. But it could help with some more
review. However, I wanted to get the patch out for review, given the
looming deadline. Similarly for the commit message.

I am adding this to CF so that it gets some CI coverage especially on
the platforms which do not support resizable shared memory.

-- 
Best Wishes,
Ashutosh Bapat


Attachments:

  [text/x-patch] v20260405-0001-refactor-Move-ShmemInitHash-to-separate-fi.patch (11.1K, 2-v20260405-0001-refactor-Move-ShmemInitHash-to-separate-fi.patch)
  download | inline diff:
From 6783e74145e4b88dca86f4bd432d618b3d389bd9 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Sat, 21 Mar 2026 13:07:28 +0200
Subject: [PATCH v20260405 01/15] refactor: Move ShmemInitHash to separate file

In preparation for next commits
---
 src/backend/storage/ipc/Makefile     |   1 +
 src/backend/storage/ipc/meson.build  |   1 +
 src/backend/storage/ipc/shmem.c      | 108 ----------------------
 src/backend/storage/ipc/shmem_hash.c | 130 +++++++++++++++++++++++++++
 src/include/storage/shmem.h          |   9 +-
 5 files changed, 139 insertions(+), 110 deletions(-)
 create mode 100644 src/backend/storage/ipc/shmem_hash.c

diff --git a/src/backend/storage/ipc/Makefile b/src/backend/storage/ipc/Makefile
index 9a07f6e1d92..f71653bbe48 100644
--- a/src/backend/storage/ipc/Makefile
+++ b/src/backend/storage/ipc/Makefile
@@ -22,6 +22,7 @@ OBJS = \
 	shm_mq.o \
 	shm_toc.o \
 	shmem.o \
+	shmem_hash.o \
 	signalfuncs.o \
 	sinval.o \
 	sinvaladt.o \
diff --git a/src/backend/storage/ipc/meson.build b/src/backend/storage/ipc/meson.build
index 9c1ca954d9d..b8c31e29967 100644
--- a/src/backend/storage/ipc/meson.build
+++ b/src/backend/storage/ipc/meson.build
@@ -14,6 +14,7 @@ backend_sources += files(
   'shm_mq.c',
   'shm_toc.c',
   'shmem.c',
+  'shmem_hash.c',
   'signalfuncs.c',
   'sinval.c',
   'sinvaladt.c',
diff --git a/src/backend/storage/ipc/shmem.c b/src/backend/storage/ipc/shmem.c
index 3cb51ad62f8..c994f7674ec 100644
--- a/src/backend/storage/ipc/shmem.c
+++ b/src/backend/storage/ipc/shmem.c
@@ -96,9 +96,6 @@ typedef struct ShmemAllocatorData
 
 #define ShmemIndexLock (&ShmemAllocator->index_lock)
 
-static HTAB *shmem_hash_create(void *location, size_t size, bool found,
-							   const char *name, int64 nelems, HASHCTL *infoP, int hash_flags);
-static void *ShmemHashAlloc(Size size, void *alloc_arg);
 static void *ShmemAllocRaw(Size size, Size *allocated_size);
 
 /* shared memory global variables */
@@ -257,29 +254,6 @@ ShmemAllocNoError(Size size)
 	return ShmemAllocRaw(size, &allocated_size);
 }
 
-/*
- * ShmemHashAlloc -- alloc callback for shared memory hash tables
- *
- * Carve out the allocation from a pre-allocated region.  All shared memory
- * hash tables are initialized with HASH_FIXED_SIZE, so all the allocations
- * happen upfront during initialization and no locking is required.
- */
-static void *
-ShmemHashAlloc(Size size, void *alloc_arg)
-{
-	shmem_hash_allocator *allocator = (shmem_hash_allocator *) alloc_arg;
-	void	   *result;
-
-	size = MAXALIGN(size);
-
-	if (allocator->end - allocator->next < size)
-		return NULL;
-	result = allocator->next;
-	allocator->next += size;
-
-	return result;
-}
-
 /*
  * ShmemAllocRaw -- allocate align chunk and return allocated size
  *
@@ -341,88 +315,6 @@ ShmemAddrIsValid(const void *addr)
 	return (addr >= ShmemBase) && (addr < ShmemEnd);
 }
 
-/*
- * ShmemInitHash -- Create and initialize, or attach to, a
- *		shared memory hash table.
- *
- * We assume caller is doing some kind of synchronization
- * so that two processes don't try to create/initialize the same
- * table at once.  (In practice, all creations are done in the postmaster
- * process; child processes should always be attaching to existing tables.)
- *
- * nelems is the maximum number of hashtable entries.
- *
- * *infoP and hash_flags must specify at least the entry sizes and key
- * comparison semantics (see hash_create()).  Flag bits and values specific
- * to shared-memory hash tables are added here, except that callers may
- * choose to specify HASH_PARTITION.
- *
- * Note: before Postgres 9.0, this function returned NULL for some failure
- * cases.  Now, it always throws error instead, so callers need not check
- * for NULL.
- */
-HTAB *
-ShmemInitHash(const char *name,		/* table string name for shmem index */
-			  int64 nelems,		/* size of the table */
-			  HASHCTL *infoP,	/* info about key and bucket size */
-			  int hash_flags)	/* info about infoP */
-{
-	bool		found;
-	size_t		size;
-	void	   *location;
-
-	size = hash_estimate_size(nelems, infoP->entrysize);
-
-	/* look it up in the shmem index or allocate */
-	location = ShmemInitStruct(name, size, &found);
-
-	return shmem_hash_create(location, size, found,
-							 name, nelems, infoP, hash_flags);
-}
-
-/*
- * Initialize or attach to a shared hash table in the given shmem region.
- *
- * This is extracted from ShmemInitHash() to allow InitShmemAllocator() to
- * share the logic for bootstrapping the ShmemIndex hash table.
- */
-static HTAB *
-shmem_hash_create(void *location, size_t size, bool found,
-				  const char *name, int64 nelems, HASHCTL *infoP, int hash_flags)
-{
-	shmem_hash_allocator allocator;
-
-	/*
-	 * Hash tables allocated in shared memory have a fixed directory and have
-	 * all elements allocated upfront.  We don't support growing because we'd
-	 * need to grow the underlying shmem region with it.
-	 *
-	 * The shared memory allocator must be specified too.
-	 */
-	infoP->alloc = ShmemHashAlloc;
-	infoP->alloc_arg = NULL;
-	hash_flags |= HASH_SHARED_MEM | HASH_ALLOC | HASH_FIXED_SIZE;
-
-	/*
-	 * if it already exists, attach to it rather than allocate and initialize
-	 * new space
-	 */
-	if (!found)
-	{
-		allocator.next = (char *) location;
-		allocator.end = (char *) location + size;
-		infoP->alloc_arg = &allocator;
-	}
-	else
-	{
-		/* Pass location of hashtable header to hash_create */
-		infoP->hctl = (HASHHDR *) location;
-		hash_flags |= HASH_ATTACH;
-	}
-
-	return hash_create(name, nelems, infoP, hash_flags);
-}
-
 /*
  * ShmemInitStruct -- Create/attach to a structure in shared memory.
  *
diff --git a/src/backend/storage/ipc/shmem_hash.c b/src/backend/storage/ipc/shmem_hash.c
new file mode 100644
index 00000000000..0b05730129e
--- /dev/null
+++ b/src/backend/storage/ipc/shmem_hash.c
@@ -0,0 +1,130 @@
+/*-------------------------------------------------------------------------
+ *
+ * shmem_hash.c
+ *	  hash table implementation in shared memory
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * A shared memory hash table implementation on top of the named, fixed-size
+ * shared memory areas managed by shmem.c.  Hash tables have a fixed maximum
+ * size, but their actual size can vary dynamically.  When entries are added
+ * to the table, more space is allocated.  Each shared data structure and hash
+ * has a string name to identify it.
+ *
+ * IDENTIFICATION
+ *	  src/backend/storage/ipc/shmem_hash.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "storage/shmem.h"
+
+static void *ShmemHashAlloc(Size size, void *alloc_arg);
+
+/*
+ * ShmemInitHash -- Create and initialize, or attach to, a
+ *		shared memory hash table.
+ *
+ * We assume caller is doing some kind of synchronization
+ * so that two processes don't try to create/initialize the same
+ * table at once.  (In practice, all creations are done in the postmaster
+ * process; child processes should always be attaching to existing tables.)
+ *
+ * nelems is the maximum number of hashtable entries.
+ *
+ * *infoP and hash_flags must specify at least the entry sizes and key
+ * comparison semantics (see hash_create()).  Flag bits and values specific
+ * to shared-memory hash tables are added here, except that callers may
+ * choose to specify HASH_PARTITION.
+ *
+ * Note: before Postgres 9.0, this function returned NULL for some failure
+ * cases.  Now, it always throws error instead, so callers need not check
+ * for NULL.
+ */
+HTAB *
+ShmemInitHash(const char *name,		/* table string name for shmem index */
+			  int64 nelems,		/* size of the table */
+			  HASHCTL *infoP,	/* info about key and bucket size */
+			  int hash_flags)	/* info about infoP */
+{
+	bool		found;
+	size_t		size;
+	void	   *location;
+
+	size = hash_estimate_size(nelems, infoP->entrysize);
+
+	/* look it up in the shmem index or allocate */
+	location = ShmemInitStruct(name, size, &found);
+
+	return shmem_hash_create(location, size, found,
+							 name, nelems, infoP, hash_flags);
+}
+
+/*
+ * Initialize or attach to a shared hash table in the given shmem region.
+ *
+ * This is extracted from ShmemInitHash() to allow InitShmemAllocator() to
+ * share the logic for bootstrapping the ShmemIndex hash table.
+ */
+HTAB *
+shmem_hash_create(void *location, size_t size, bool found,
+				  const char *name, int64 nelems, HASHCTL *infoP, int hash_flags)
+{
+	shmem_hash_allocator allocator;
+
+	/*
+	 * Hash tables allocated in shared memory have a fixed directory and have
+	 * all elements allocated upfront.  We don't support growing because we'd
+	 * need to grow the underlying shmem region with it.
+	 *
+	 * The shared memory allocator must be specified too.
+	 */
+	infoP->alloc = ShmemHashAlloc;
+	infoP->alloc_arg = NULL;
+	hash_flags |= HASH_SHARED_MEM | HASH_ALLOC | HASH_FIXED_SIZE;
+
+	/*
+	 * if it already exists, attach to it rather than allocate and initialize
+	 * new space
+	 */
+	if (!found)
+	{
+		allocator.next = (char *) location;
+		allocator.end = (char *) location + size;
+		infoP->alloc_arg = &allocator;
+	}
+	else
+	{
+		/* Pass location of hashtable header to hash_create */
+		infoP->hctl = (HASHHDR *) location;
+		hash_flags |= HASH_ATTACH;
+	}
+
+	return hash_create(name, nelems, infoP, hash_flags);
+}
+
+/*
+ * ShmemHashAlloc -- alloc callback for shared memory hash tables
+ *
+ * Carve out the allocation from a pre-allocated region.  All shared memory
+ * hash tables are initialized with HASH_FIXED_SIZE, so all the allocations
+ * happen upfront during initialization and no locking is required.
+ */
+static void *
+ShmemHashAlloc(Size size, void *alloc_arg)
+{
+	shmem_hash_allocator *allocator = (shmem_hash_allocator *) alloc_arg;
+	void	   *result;
+
+	size = MAXALIGN(size);
+
+	if (allocator->end - allocator->next < size)
+		return NULL;
+	result = allocator->next;
+	allocator->next += size;
+
+	return result;
+}
diff --git a/src/include/storage/shmem.h b/src/include/storage/shmem.h
index a2eb499d63c..82f5403c952 100644
--- a/src/include/storage/shmem.h
+++ b/src/include/storage/shmem.h
@@ -30,15 +30,20 @@ typedef struct PGShmemHeader PGShmemHeader; /* avoid including
 extern void InitShmemAllocator(PGShmemHeader *seghdr);
 extern void *ShmemAlloc(Size size);
 extern void *ShmemAllocNoError(Size size);
+extern void *ShmemHashAlloc(Size size, void *alloc_arg);
 extern bool ShmemAddrIsValid(const void *addr);
-extern HTAB *ShmemInitHash(const char *name, int64 nelems,
-						   HASHCTL *infoP, int hash_flags);
 extern void *ShmemInitStruct(const char *name, Size size, bool *foundPtr);
 extern Size add_size(Size s1, Size s2);
 extern Size mul_size(Size s1, Size s2);
 
 extern PGDLLIMPORT Size pg_get_shmem_pagesize(void);
 
+/* shmem_hash.c */
+extern HTAB *ShmemInitHash(const char *name, int64 nelems,
+						   HASHCTL *infoP, int hash_flags);
+extern HTAB *shmem_hash_create(void *location, size_t size, bool found,
+							   const char *name, int64 nelems, HASHCTL *infoP, int hash_flags)
+
 /* ipci.c */
 extern void RequestAddinShmemSpace(Size size);
 

base-commit: f63ca3379025ee4547865182da6cae14aec35d58
-- 
2.34.1



  [text/x-patch] v20260405-0002-Introduce-a-new-mechanism-for-registering-.patch (62.3K, 3-v20260405-0002-Introduce-a-new-mechanism-for-registering-.patch)
  download | inline diff:
From d2d065d866f7275b8e6706757240e39a0e34d2c0 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Wed, 1 Apr 2026 20:01:39 +0300
Subject: [PATCH v20260405 02/15] Introduce a new mechanism for registering
 shared memory areas

This merges the separate [Subsystem]ShmemSize() and
[Subsystem]ShmemInit() phases at postmaster startup. Each subsystem is
now called into just once, before the shared memory segment has been
allocated, to register or "request" the subsystem's shared memory
needs. This is more ergonomic, as you only need to calculate the size
once.

This replaces ShmemInitStruct() and ShmemInitHash(), which become just
backwards-compatibility wrappers around the new functions. In future
commits, I plan to replace all ShmemInitStruct() and ShmemInitHash()
calls with the new functions, although we'll still need to keep them
around for extensions.

Reviewed-by: Ashutosh Bapat <[email protected]>
Reviewed-by: Zsolt Parragi <[email protected]>
Reviewed-by: Robert Haas <[email protected]>
Reviewed-by: Daniel Gustafsson <[email protected]>
Discussion: https://www.postgresql.org/message-id/CAExHW5vM1bneLYfg0wGeAa=52UiJ3z4vKd3AJ72X8Fw6k3KKrg@mail.gmail.com
---
 doc/src/sgml/system-views.sgml          |   4 +-
 doc/src/sgml/xfunc.sgml                 | 162 +++--
 src/backend/bootstrap/bootstrap.c       |   2 +
 src/backend/postmaster/launch_backend.c |   5 +
 src/backend/postmaster/postmaster.c     |  19 +-
 src/backend/storage/ipc/ipci.c          |  30 +-
 src/backend/storage/ipc/shmem.c         | 832 ++++++++++++++++++++----
 src/backend/storage/ipc/shmem_hash.c    |  86 ++-
 src/backend/storage/lmgr/proc.c         |   3 +
 src/backend/tcop/postgres.c             |  10 +-
 src/include/storage/shmem.h             | 183 +++++-
 src/include/storage/shmem_internal.h    |  52 ++
 src/tools/pgindent/typedefs.list        |   9 +-
 13 files changed, 1190 insertions(+), 207 deletions(-)
 create mode 100644 src/include/storage/shmem_internal.h

diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 9ee1a2bfc6a..2ebec6928d5 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -4254,8 +4254,8 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
   <para>
    Anonymous allocations are allocations that have been made
    with <literal>ShmemAlloc()</literal> directly, rather than via
-   <literal>ShmemInitStruct()</literal> or
-   <literal>ShmemInitHash()</literal>.
+   <literal>ShmemRequestStruct()</literal> or
+   <literal>ShmemRequestHash()</literal>.
   </para>
 
   <para>
diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 70e815b8a2c..aed3f2f0071 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -3628,71 +3628,132 @@ CREATE FUNCTION make_array(anyelement) RETURNS anyarray
       Add-ins can reserve shared memory on server startup.  To do so, the
       add-in's shared library must be preloaded by specifying it in
       <xref linkend="guc-shared-preload-libraries"/><indexterm><primary>shared_preload_libraries</primary></indexterm>.
-      The shared library should also register a
-      <literal>shmem_request_hook</literal> in its
-      <function>_PG_init</function> function.  This
-      <literal>shmem_request_hook</literal> can reserve shared memory by
-      calling:
+      The shared library should register callbacks in
+      its <function>_PG_init</function> function, which then get called at the
+      right stages of the system startup to initialize the shared memory.
+      Here is an example:
 <programlisting>
-void RequestAddinShmemSpace(Size size)
-</programlisting>
-      Each backend should obtain a pointer to the reserved shared memory by
-      calling:
-<programlisting>
-void *ShmemInitStruct(const char *name, Size size, bool *foundPtr)
-</programlisting>
-      If this function sets <literal>foundPtr</literal> to
-      <literal>false</literal>, the caller should proceed to initialize the
-      contents of the reserved shared memory.  If <literal>foundPtr</literal>
-      is set to <literal>true</literal>, the shared memory was already
-      initialized by another backend, and the caller need not initialize
-      further.
-     </para>
+typedef struct MyShmemData {
+    LWLock      lock;    /* protects the fields below */
 
-     <para>
-      To avoid race conditions, each backend should use the LWLock
-      <function>AddinShmemInitLock</function> when initializing its allocation
-      of shared memory, as shown here:
-<programlisting>
-static mystruct *ptr = NULL;
-bool        found;
+    ... shared memory contents ...
+} MyShmemData;
+
+static MyShmemData *MyShmem;    /* pointer to the struct in shared memory */
+
+static void my_shmem_request(void *arg);
+static void my_shmem_init(void *arg);
+
+const ShmemCallbacks my_shmem_callbacks = {
+    .request_fn = my_shmem_request,
+    .init_fn = my_shmem_init,
+};
+
+/*
+ * Module load callback
+ */
+void
+_PG_init(void)
+{
+    /*
+     * In order to create our shared memory area, we have to be loaded via
+     * shared_preload_libraries.
+     */
+    if (!process_shared_preload_libraries_in_progress)
+        return;
+
+    /* Register our shared memory needs */
+    RegisterShmemCallbacks(&amp;my_shmem_callbacks);
+}
+
+/* callback to request  */
+static void
+my_shmem_request(void *arg)
+{
+    /* A persistent handle to the shared memory area in this backend */
+    static ShmemStructDesc MyShmemDesc;
+
+    ShmemRequestStruct(&amp;MyShmemDesc,
+                       .name = "My shmem area",
+                       .size = sizeof(MyShmemData),
+                       .ptr = (void **) &amp;MyShmem,
+        );
+}
 
-LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
-ptr = ShmemInitStruct("my struct name", size, &amp;found);
-if (!found)
+/* callback to initialize the contents of the MyShmem area at startup */
+static void
+my_shmem_init(void *arg)
 {
-    ... initialize contents of shared memory ...
-    ptr->locks = GetNamedLWLockTranche("my tranche name");
+    int         tranche_id;
+
+    /* Initialize the lock */
+    tranche_id = LWLockNewTrancheId("my tranche name");
+    LWLockInitialize(&amp;MyShmem->lock, tranche_id);
+
+    ... initialize the rest of MyShmem fields ...
 }
-LWLockRelease(AddinShmemInitLock);
+
 </programlisting>
-      <literal>shmem_startup_hook</literal> provides a convenient place for the
-      initialization code, but it is not strictly required that all such code
-      be placed in this hook.  On Windows (and anywhere else where
-      <literal>EXEC_BACKEND</literal> is defined), each backend executes the
-      registered <literal>shmem_startup_hook</literal> shortly after it
-      attaches to shared memory, so add-ins should still acquire
-      <function>AddinShmemInitLock</function> within this hook, as shown in the
-      example above.  On other platforms, only the postmaster process executes
-      the <literal>shmem_startup_hook</literal>, and each backend automatically
-      inherits the pointers to shared memory.
+      The <function>request_fn</function> callback is called during system
+      startup, before the shared memory has been allocated. It should call
+      <function>ShmemRequestStruct()</function> to register the add-in's
+      shared memory needs. Note that <function>ShmemRequestStruct()</function>
+      doesn't immediately allocate or initialize the memory, it merely
+      registers the space to be allocated later in the startup sequence.  When
+      the memory is allocated, it is initialized to zero.  For any more
+      complex initialization, set the <function>init_fn()</function> callback,
+      which will be called after the memory has been allocated and initialized
+      to zero, but before any other processes are running, and thus no locking
+      is required.
      </para>
-
      <para>
-      An example of a <literal>shmem_request_hook</literal> and
-      <literal>shmem_startup_hook</literal> can be found in
+      On Windows, the <function>attach_fn</function> callback, if any, is
+      additionally called at every backend startup.  It can be used to
+      initialize additional per-backend state related to the shared memory
+      area that is inherited via <function>fork()</function> on other systems.
+     </para>
+     <para>
+      An example of allocating shared memory can be found in
       <filename>contrib/pg_stat_statements/pg_stat_statements.c</filename> in
       the <productname>PostgreSQL</productname> source tree.
      </para>
     </sect3>
 
     <sect3 id="xfunc-shared-addin-after-startup">
-     <title>Requesting Shared Memory After Startup</title>
+     <title>Requesting Shared Memory After Startup with <function>ShmemRequestStruct</function></title>
+
+     <para>
+      The <function>ShmemRequestStruct()</function> can also be called after
+      system startup, which is useful to allow small allocations in add-in
+      libraries that are not specified in
+      <xref linkend="guc-shared-preload-libraries"/><indexterm><primary>shared_preload_libraries</primary></indexterm>.
+      However, after startup the allocation can fail if there is not enough
+      shared memory available. The system reserves some memory for allocations
+      after startup, but that reservation is small.
+     </para>
+     <para>
+      By default, <function>RegisterShmemCallbacks()</function> fails with an
+      error if called after system startup. To use it after startup, you must
+      set the <literal>SHMEM_CALLBACKS_ALLOW_AFTER_STARTUP</literal> flag in
+      the argument <structname>ShmemCallbacks</structname> struct to
+      acknowledge the risk.
+     </para>
+     <para>
+      When <function>RegisterShmemCallbacks()</function> is called after
+      startup, it will immediately call the appropriate callbacks, depending
+      on whether the requested memory areas were already initialized by
+      another backend. The callbacks will be called while holding an internal
+      lock, which prevents concurrent two backends from initializating the
+      memory area concurrently.
+     </para>
+    </sect3>
+
+    <sect3 id="xfunc-shared-addin-dynamic">
+     <title>Allocating Dynamic Shared Memory After Startup</title>
 
      <para>
       There is another, more flexible method of reserving shared memory that
-      can be done after server startup and outside a
-      <literal>shmem_request_hook</literal>.  To do so, each backend that will
+      can be done after server startup.  To do so, each backend that will
       use the shared memory should obtain a pointer to it by calling:
 <programlisting>
 void *GetNamedDSMSegment(const char *name, size_t size,
@@ -3711,10 +3772,7 @@ void *GetNamedDSMSegment(const char *name, size_t size,
      </para>
 
      <para>
-      Unlike shared memory reserved at server startup, there is no need to
-      acquire <function>AddinShmemInitLock</function> or otherwise take action
-      to avoid race conditions when reserving shared memory with
-      <function>GetNamedDSMSegment</function>.  This function ensures that only
+      <function>GetNamedDSMSegment</function> ensures that only
       one backend allocates and initializes the segment and that all other
       backends receive a pointer to the fully allocated and initialized
       segment.
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index c52c0a6023d..c707ccfa563 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -39,6 +39,7 @@
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/proc.h"
+#include "storage/shmem_internal.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -373,6 +374,7 @@ BootstrapModeMain(int argc, char *argv[], bool check_only)
 
 	InitializeFastPathLocks();
 
+	ShmemCallRequestCallbacks();
 	CreateSharedMemoryAndSemaphores();
 
 	/*
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 434e0643022..0973010b7dc 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -49,7 +49,9 @@
 #include "replication/walreceiver.h"
 #include "storage/dsm.h"
 #include "storage/io_worker.h"
+#include "storage/ipc.h"
 #include "storage/pg_shmem.h"
+#include "storage/shmem_internal.h"
 #include "tcop/backend_startup.h"
 #include "utils/memutils.h"
 
@@ -672,7 +674,10 @@ SubPostmasterMain(int argc, char *argv[])
 
 	/* Restore basic shared memory pointers */
 	if (UsedShmemSegAddr != NULL)
+	{
 		InitShmemAllocator(UsedShmemSegAddr);
+		ShmemCallRequestCallbacks();
+	}
 
 	/*
 	 * Run the appropriate Main function
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index eb4f3eb72d4..693475014fe 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -115,6 +115,7 @@
 #include "storage/ipc.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
+#include "storage/shmem_internal.h"
 #include "tcop/backend_startup.h"
 #include "tcop/tcopprot.h"
 #include "utils/datetime.h"
@@ -951,7 +952,14 @@ PostmasterMain(int argc, char *argv[])
 	InitializeFastPathLocks();
 
 	/*
-	 * Give preloaded libraries a chance to request additional shared memory.
+	 * Ask all subsystems, including preloaded libraries, to register their
+	 * shared memory needs.
+	 */
+	ShmemCallRequestCallbacks();
+
+	/*
+	 * Also call any legacy shmem request hooks that might'be been installed
+	 * by preloaded libraries.
 	 */
 	process_shmem_requests();
 
@@ -3232,7 +3240,14 @@ PostmasterStateMachine(void)
 		/* re-read control file into local memory */
 		LocalProcessControlFile(true);
 
-		/* re-create shared memory and semaphores */
+		/*
+		 * Re-initialize shared memory and semaphores.  Note: We don't call
+		 * RegisterBuiltinShmemCallbacks(), we keep the old registrations.  In
+		 * order to re-register structs in extensions, we'd need to reload
+		 * shared preload libraries, and we don't want to do that.
+		 */
+		ResetShmemAllocator();
+		ShmemCallRequestCallbacks();
 		CreateSharedMemoryAndSemaphores();
 
 		UpdatePMState(PM_STARTUP);
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 7aab5da3386..24422a80ab3 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -50,6 +50,7 @@
 #include "storage/proc.h"
 #include "storage/procarray.h"
 #include "storage/procsignal.h"
+#include "storage/shmem_internal.h"
 #include "storage/sinvaladt.h"
 #include "utils/guc.h"
 #include "utils/injection_point.h"
@@ -100,8 +101,9 @@ CalculateShmemSize(void)
 	 * during the actual allocation phase.
 	 */
 	size = 100000;
-	size = add_size(size, hash_estimate_size(SHMEM_INDEX_SIZE,
-											 sizeof(ShmemIndexEnt)));
+	size = add_size(size, ShmemGetRequestedSize());
+
+	/* legacy subsystems */
 	size = add_size(size, dsm_estimate_size());
 	size = add_size(size, DSMRegistryShmemSize());
 	size = add_size(size, BufferManagerShmemSize());
@@ -176,6 +178,13 @@ AttachSharedMemoryStructs(void)
 	 */
 	InitializeFastPathLocks();
 
+	/*
+	 * Attach to LWLocks first. They are needed by most other subsystems.
+	 */
+	LWLockShmemInit();
+
+	/* Establish pointers to all shared memory areas in this backend */
+	ShmemAttachRequested();
 	CreateOrAttachShmemStructs();
 
 	/*
@@ -220,7 +229,17 @@ CreateSharedMemoryAndSemaphores(void)
 	 */
 	InitShmemAllocator(seghdr);
 
-	/* Initialize subsystems */
+	/*
+	 * Initialize LWLocks first, in case any of the shmem init function use
+	 * LWLocks.  (Nothing else can be running during startup, so they don't
+	 * need to do any locking yet, but we nevertheless allow it.)
+	 */
+	LWLockShmemInit();
+
+	/* Initialize all shmem areas */
+	ShmemInitRequested();
+
+	/* Initialize legacy subsystems */
 	CreateOrAttachShmemStructs();
 
 	/* Initialize dynamic shared memory facilities. */
@@ -251,11 +270,6 @@ CreateSharedMemoryAndSemaphores(void)
 static void
 CreateOrAttachShmemStructs(void)
 {
-	/*
-	 * Set up LWLocks.  They are needed by most other subsystems.
-	 */
-	LWLockShmemInit();
-
 	dsm_shmem_init();
 	DSMRegistryShmemInit();
 
diff --git a/src/backend/storage/ipc/shmem.c b/src/backend/storage/ipc/shmem.c
index c994f7674ec..29ff6065dda 100644
--- a/src/backend/storage/ipc/shmem.c
+++ b/src/backend/storage/ipc/shmem.c
@@ -19,43 +19,115 @@
  * methods).  The routines in this file are used for allocating and
  * binding to shared memory data structures.
  *
- * NOTES:
- *		(a) There are three kinds of shared memory data structures
- *	available to POSTGRES: fixed-size structures, queues and hash
- *	tables.  Fixed-size structures contain things like global variables
- *	for a module and should never be allocated after the shared memory
- *	initialization phase.  Hash tables have a fixed maximum size and
- *	cannot grow beyond that.  Queues link data structures
- *	that have been allocated either within fixed-size structures or as hash
- *	buckets.  Each shared data structure has a string name to identify
- *	it (assigned in the module that declares it).
- *
- *		(b) During initialization, each module looks for its
- *	shared data structures in a hash table called the "Shmem Index".
- *	If the data structure is not present, the caller can allocate
- *	a new one and initialize it.  If the data structure is present,
- *	the caller "attaches" to the structure by initializing a pointer
- *	in the local address space.
- *		The shmem index has two purposes: first, it gives us
- *	a simple model of how the world looks when a backend process
- *	initializes.  If something is present in the shmem index,
- *	it is initialized.  If it is not, it is uninitialized.  Second,
- *	the shmem index allows us to allocate shared memory on demand
- *	instead of trying to preallocate structures and hard-wire the
- *	sizes and locations in header files.  If you are using a lot
- *	of shared memory in a lot of different places (and changing
- *	things during development), this is important.
- *
- *		(c) In standard Unix-ish environments, individual backends do not
- *	need to re-establish their local pointers into shared memory, because
- *	they inherit correct values of those variables via fork() from the
- *	postmaster.  However, this does not work in the EXEC_BACKEND case.
- *	In ports using EXEC_BACKEND, new backends have to set up their local
- *	pointers using the method described in (b) above.
- *
- *		(d) memory allocation model: shared memory can never be
- *	freed, once allocated.   Each hash table has its own free list,
- *	so hash buckets can be reused when an item is deleted.
+ * 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.
+ *
+ * Shared memory areas should usually not be allocated after postmaster
+ * startup, although we do allow small allocations later for the benefit of
+ * extension modules that are loaded after startup.  Despite that allowance,
+ * extensions that need shared memory should be added in
+ * shared_preload_libraries, because the allowance is quite small and there is
+ * no guarantee that any memory is available after startup.
+ *
+ * Nowadays, there is also a third way to allocate shared memory called
+ * Dynamic Shared Memory.  See dsm.c for that facility.  One big difference
+ * between traditional shared memory handled by shmem.c and dynamic shared
+ * memory is that traditional shared memory areas are mapped to the same
+ * address in all processes, so you can use normal pointers in shared memory
+ * structs.  With Dynamic Shared Memory, you must use offsets or DSA pointers
+ * instead.
+ *
+ * Shared memory managed by shmem.c can never be freed, once allocated.  Each
+ * hash table has its own free list, so hash buckets can be reused when an
+ * item is deleted.  However, if one hash table grows very large and then
+ * shrinks, its space cannot be redistributed to other tables.  We could build
+ * a simple hash bucket garbage collector if need be.  Right now, it seems
+ * unnecessary.
+ *
+ * Usage
+ * -----
+ *
+ * To allocate shared memory, you need to register a set of callback functions
+ * which handle the lifecycle of the allocation.  In the request_fn callback,
+ * fill in a ShmemRequestStructOpts struct with the name, size, and any other
+ * options, and call ShmemRequestStruct().  Leave any unused fields as zeros.
+ *
+ *	typedef struct MyShmemData {
+ *		...
+ *	} MyShmemData;
+ *
+ *	static MyShmemData *MyShmem;
+ *
+ *	static void my_shmem_request(void *arg);
+ *	static void my_shmem_init(void *arg);
+ *
+ *  const ShmemCallbacks MyShmemCallbacks = {
+ *		.request_fn = my_shmem_request,
+ *		.init_fn = my_shmem_init,
+ *	};
+ *
+ *	static void
+ *	my_shmem_request(void *arg)
+ *	{
+ *		static ShmemStructDesc MyShmemDesc;
+ *
+ *		ShmemRequestStruct(&MyShmemDesc, &(ShmemRequestStructOpts) {
+ *			.name = "My shmem area",
+ *			.size = sizeof(MyShmemData),
+ *			.ptr = (void **) &MyShmem,
+ *		});
+ *	}
+ *
+ * In builtin PostgreSQL code, add the callbacks to the list in
+ * src/include/storage/subsystemlist.h. In an add-in module, you can register
+ * the callbacks by calling RegisterShmemCallbacks(&MyShmemCallbacks) in the
+ * extension's _PG_init() function.
+ *
+ * Lifecycle
+ * ---------
+ *
+ * Initializing shared memory happens in multiple phases. In the first phase,
+ * during postmaster startup, all the request_fn callbacks are called.  Only
+ * after all the request_fn callbacks have been called and all the shmem areas
+ * have been requested by the ShmemRequestStruct() calls we know how much
+ * shared memory we need in total. After that, postmaster allocates global
+ * shared memory segment, and calls all the init_fn callbacks to initialize
+ * all the requested shmem areas.
+ *
+ * In standard Unix-ish environments, individual backends do not need to
+ * re-establish their local pointers into shared memory, because they inherit
+ * correct values of those variables via fork() from the postmaster.  However,
+ * this does not work in the EXEC_BACKEND case.  In ports using EXEC_BACKEND,
+ * backend startup also calls the shmem_request callbacks to re-establish the
+ * knowledge about each shared memory area, sets the pointer variables
+ * (*ShmemStructDesc->ptr), and calls the attach_fn callback, if any, for
+ * additional per-backend setup.
+ *
+ * Legacy ShmemInitStruct()/ShmemInitHash() functions
+ * --------------------------------------------------
+ *
+ * ShmemInitStruct()/ShmemInitHash() is another way of registering shmem
+ * areas.  It pre-dates the ShmemRequestStruct()/ShmemRequestHash() functions,
+ * and should not be used in new code, but as of this writing it is still
+ * widely used in extensions.
+ *
+ * To allocate a shmem area with ShmemInitStruct(), you need to separately
+ * register the size needed for the area by calling RequestAddinShmemSpace()
+ * from the extension's shmem_request_hook, and allocate the area by calling
+ * ShmemInitStruct() from the extension's shmem_startup_hook.  There are no
+ * init/attach callbacks.  Instead, the caller of ShmemInitStruct() must check
+ * the return status of ShmemInitStruct() and initialize the struct if it was
+ * not previously initialized.
+ *
+ * Calling ShmemAlloc() directly
+ * -----------------------------
+ *
+ * There's a more low-level way of allocating shared memory too: you can call
+ * ShmemAlloc() directly.  It's used to implement the higher level mechanisms,
+ * and should generally not be called directly.
  */
 
 #include "postgres.h"
@@ -70,10 +142,80 @@
 #include "storage/lwlock.h"
 #include "storage/pg_shmem.h"
 #include "storage/shmem.h"
+#include "storage/shmem_internal.h"
 #include "storage/spin.h"
 #include "utils/builtins.h"
 #include "utils/tuplestore.h"
 
+/*
+ * Registered callbacks.
+ *
+ * During postmaster startup, we accumulate the callbacks from all subsystems
+ * in this list.
+ *
+ * This is in process private memory, although on Unix-like systems, we expect
+ * all the registrations to happen at postmaster startup time and be inherited
+ * by all the child processes via fork().
+ */
+static List *registered_shmem_callbacks;
+
+/*
+ * In the shmem request phase, all the shmem areas requested with the
+ * ShmemRequest*() functions are accumulated here.
+ */
+typedef struct
+{
+	ShmemStructOpts *options;
+	ShmemRequestKind kind;
+} ShmemRequest;
+
+static List *pending_shmem_requests;
+
+/*
+ * Per-process state machine, for sanity checking that we do things in the
+ * right order.
+ *
+ * Postmaster:
+ *   INITIAL -> REQUESTING -> INITIALIZING -> DONE
+ *
+ * Backends in EXEC_BACKEND mode:
+ *   INITIAL -> REQUESTING -> ATTACHING -> DONE
+ *
+ * Late request:
+ *   DONE -> REQUESTING -> AFTER_STARTUP_ATTACH_OR_INIT -> DONE
+ */
+enum shmem_request_state
+{
+	/* Initial state */
+	SRS_INITIAL,
+
+	/*
+	 * When we start calling the shmem_request callbacks, we enter the
+	 * SRS_REQUESTING phase.  All ShmemRequestStruct calls happen in this
+	 * state.
+	 */
+	SRS_REQUESTING,
+
+	/*
+	 * Postmaster has finished all shmem requests, and is now initializing the
+	 * shared memory segment.  init_fn callbacks are called in this state.
+	 */
+	SRS_INITIALIZING,
+
+	/*
+	 * A postmaster child process is starting up.  attach_fn callbacks are
+	 * called in this state.
+	 */
+	SRS_ATTACHING,
+
+	/* An after-startup allocation or attachment is in progress. */
+	SRS_AFTER_STARTUP_ATTACH_OR_INIT,
+
+	/* Normal state after shmem initialization / attachment */
+	SRS_DONE,
+};
+static enum shmem_request_state shmem_request_state = SRS_INITIAL;
+
 /*
  * This is the first data structure stored in the shared memory segment, at
  * the offset that PGShmemHeader->content_offset points to.  Allocations by
@@ -105,35 +247,379 @@ static void *ShmemBase;			/* start address of shared memory */
 static void *ShmemEnd;			/* end+1 address of shared memory */
 
 static ShmemAllocatorData *ShmemAllocator;
-static HTAB *ShmemIndex = NULL; /* primary index hashtable for shmem */
+
+/*
+ * ShmemIndex is a global directory of shmem areas, itself also stored in the
+ * shared memory.
+ */
+static HTAB *ShmemIndex;
+
+ /* max size of data structure string name */
+#define SHMEM_INDEX_KEYSIZE		 (48)
+
+/*
+ * # of additional entries to reserve in the shmem index table, for
+ * allocations after postmaster startup.  (This is not a hard limit, the hash
+ * table can grow larger than that if there is shared memory available)
+ */
+#define SHMEM_INDEX_ADDITIONAL_SIZE		 (128)
+
+/* this is a hash bucket in the shmem index table */
+typedef struct
+{
+	char		key[SHMEM_INDEX_KEYSIZE];	/* string name */
+	void	   *location;		/* location in shared mem */
+	Size		size;			/* # bytes requested for the structure */
+	Size		allocated_size; /* # bytes actually allocated */
+} ShmemIndexEnt;
 
 /* To get reliable results for NUMA inquiry we need to "touch pages" once */
 static bool firstNumaTouch = true;
 
+static void CallShmemCallbacksAfterStartup(const ShmemCallbacks *callbacks);
+static void InitShmemIndexEntry(ShmemRequest *request);
+static bool AttachShmemIndexEntry(ShmemRequest *request, bool missing_ok);
+
 Datum		pg_numa_available(PG_FUNCTION_ARGS);
 
 /*
- * A very simple allocator used to carve out different parts of a hash table
- * from a previously allocated contiguous shared memory area.
+ *	ShmemRequestStruct() --- request a named shared memory area
+ *
+ * Subsystems call this to register their shared memory needs.  This is
+ * usually done early in postmaster startup, before the shared memory segment
+ * has been created, so that the size can be included in the estimate for
+ * total amount of shared memory needed.  We set aside a small amount of
+ * memory for allocations that happen later, for the benefit of non-preloaded
+ * extensions, but that should not be relied upon.
+ *
+ * This does not yet allocate the memory, but merely register the need for it.
+ * The actual allocation happens later in the postmaster startup sequence.
+ *
+ * This must be called from a shmem_request callback function, registered with
+ * RegisterShmemCallbacks().  This enforces a coding pattern that works the
+ * same in normal Unix systems and with EXEC_BACKEND.  On Unix systems, the
+ * shmem_request callbacks are called once, early in postmaster startup, and
+ * the child processes inherit the struct descriptors and any other
+ * per-process state from the postmaster.  In EXEC_BACKEND mode, shmem_request
+ * callbacks are *also* called in each backend, at backend startup, to
+ * re-establish the struct descriptors.  By calling the same function in both
+ * cases, we ensure that all the shmem areas are registered the same way in
+ * all processes.
+ *
+ * 'desc' is a backend-private handle for the shared memory area.
+ *
+ * 'options' defines the name and size of the area, and any other optional
+ * features.  Leave unused options as zeros.  The options are copied to
+ * longer-lived memory, so it doesn't need to live after the
+ * ShmemRequestStruct() call and can point to a local variable in the calling
+ * function.  The 'name' must point to a long-lived string though, only the
+ * pointer to it is copied.
+ */
+void
+ShmemRequestStructWithOpts(const ShmemStructOpts *options)
+{
+	ShmemStructOpts *options_copy;
+
+	options_copy = MemoryContextAlloc(TopMemoryContext,
+									  sizeof(ShmemStructOpts));
+	memcpy(options_copy, options, sizeof(ShmemStructOpts));
+
+	ShmemRequestInternal(options_copy, SHMEM_KIND_STRUCT);
+}
+
+/*
+ * Internal workhorse of ShmemRequestStruct() and ShmemRequestHash().
+ *
+ * Note: 'desc' and 'options' must live until the init/attach callbacks have
+ * been called.  Unlike in the public ShmemRequestStruct() and
+ * ShmemRequestHash() functions, 'options' is *not* copied.  This allows
+ * ShmemRequestHash() to pass a pointer to the extended ShmemRequestHashOpts
+ * struct instead.
+ */
+void
+ShmemRequestInternal(ShmemStructOpts *options, ShmemRequestKind kind)
+{
+	ShmemRequest *request;
+
+	if (options->name == NULL)
+		elog(ERROR, "shared memory request is missing 'name' option");
+
+	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);
+	}
+	else
+	{
+		if (options->size == SHMEM_ATTACH_UNKNOWN_SIZE)
+			elog(ERROR, "SHMEM_ATTACH_UNKNOWN_SIZE cannot be used during startup");
+		if (options->size <= 0)
+			elog(ERROR, "invalid size %zd for shared memory request for \"%s\"",
+				 options->size, options->name);
+	}
+
+	if (shmem_request_state != SRS_REQUESTING)
+		elog(ERROR, "ShmemRequestStruct can only be called from a shmem_request callback");
+
+	/* Check that it's not already registered in this process */
+	foreach_ptr(ShmemRequest, existing, pending_shmem_requests)
+	{
+		if (strcmp(existing->options->name, options->name) == 0)
+			ereport(ERROR,
+					(errmsg("shared memory struct \"%s\" is already registered",
+							options->name)));
+	}
+
+	request = palloc(sizeof(ShmemRequest));
+	request->options = options;
+	request->kind = kind;
+	pending_shmem_requests = lappend(pending_shmem_requests, request);
+}
+
+/*
+ *	ShmemGetRequestedSize() --- estimate the total size of all registered shared
+ *                              memory structures.
+ *
+ * This is called once at postmaster startup, before the shared memory segment
+ * has been created.
+ */
+size_t
+ShmemGetRequestedSize(void)
+{
+	size_t		size;
+
+	/* memory needed for the ShmemIndex */
+	size = hash_estimate_size(list_length(pending_shmem_requests) + SHMEM_INDEX_ADDITIONAL_SIZE,
+							  sizeof(ShmemIndexEnt));
+	size = CACHELINEALIGN(size);
+
+	/* memory needed for all the requested areas */
+	foreach_ptr(ShmemRequest, request, pending_shmem_requests)
+	{
+		size = add_size(size, request->options->size);
+		/* calculate alignment padding like ShmemAllocRaw() does */
+		size = CACHELINEALIGN(size);
+	}
+
+	return size;
+}
+
+/*
+ *	ShmemInitRequested() --- allocate and initialize requested shared memory
+ *                            structures.
+ *
+ * This is called once at postmaster startup, after the shared memory segment
+ * has been created.
+ */
+void
+ShmemInitRequested(void)
+{
+	/* Should be called only by the postmaster or a standalone backend. */
+	Assert(!IsUnderPostmaster);
+	Assert(shmem_request_state == SRS_INITIALIZING);
+
+	/*
+	 * Initialize the ShmemIndex entries and perform basic initialization of
+	 * all the requested memory areas.  There are no concurrent processes yet,
+	 * so no need for locking.
+	 */
+	foreach_ptr(ShmemRequest, request, pending_shmem_requests)
+	{
+		InitShmemIndexEntry(request);
+	}
+	list_free_deep(pending_shmem_requests);
+	pending_shmem_requests = NIL;
+
+	/*
+	 * Call the subsystem-specific init callbacks to finish initialization of
+	 * all the areas.
+	 */
+	foreach_ptr(const ShmemCallbacks, callbacks, registered_shmem_callbacks)
+	{
+		if (callbacks->init_fn)
+			callbacks->init_fn(callbacks->init_fn_arg);
+	}
+
+	shmem_request_state = SRS_DONE;
+}
+
+/*
+ * Re-establish process private state related to shmem areas.
+ *
+ * This is called at backend startup in EXEC_BACKEND mode, in every backend.
+ */
+#ifdef EXEC_BACKEND
+void
+ShmemAttachRequested(void)
+{
+	ListCell   *lc;
+
+	/* Must be initializing a (non-standalone) backend */
+	Assert(IsUnderPostmaster);
+	Assert(ShmemAllocator->index != NULL);
+	Assert(shmem_request_state == SRS_REQUESTING);
+	shmem_request_state = SRS_ATTACHING;
+
+	LWLockAcquire(ShmemIndexLock, LW_SHARED);
+
+	/*
+	 * Attach to all the requested memory areas.
+	 */
+	foreach_ptr(ShmemRequest, request, pending_shmem_requests)
+	{
+		AttachShmemIndexEntry(request, false);
+	}
+	list_free_deep(pending_shmem_requests);
+	pending_shmem_requests = NIL;
+
+	/* Call attach callbacks */
+	foreach(lc, registered_shmem_callbacks)
+	{
+		const ShmemCallbacks *callbacks = (const ShmemCallbacks *) lfirst(lc);
+
+		if (callbacks->attach_fn)
+			callbacks->attach_fn(callbacks->attach_fn_arg);
+	}
+
+	LWLockRelease(ShmemIndexLock);
+
+	shmem_request_state = SRS_DONE;
+}
+#endif
+
+/*
+ * Insert requested shmem area into the shared memory index and initialize it.
+ *
+ * Note that this only does performs basic initialization depending on
+ * ShmemRequestKind, like setting the global pointer variable to the area for
+ * SHMEM_KIND_STRUCT or setting up the backend-private HTAB control struct.
+ * This does *not* call the subsystem-specific init callbacks.  That's done
+ * later after all the shmem areas have been initialized or attached to.
  */
-typedef struct shmem_hash_allocator
+static void
+InitShmemIndexEntry(ShmemRequest *request)
 {
-	char	   *next;			/* start of free space in the area */
-	char	   *end;			/* end of the shmem area */
-} shmem_hash_allocator;
+	const char *name = request->options->name;
+	ShmemIndexEnt *index_entry;
+	bool		found;
+	size_t		allocated_size;
+	void	   *structPtr;
+
+	/* look it up in the shmem index */
+	index_entry = (ShmemIndexEnt *)
+		hash_search(ShmemIndex, name, HASH_ENTER_NULL, &found);
+	if (found)
+		elog(ERROR, "shared memory struct \"%s\" is already initialized", name);
+	if (!index_entry)
+	{
+		/* tried to add it to the hash table, but there was no space */
+		ereport(ERROR,
+				(errcode(ERRCODE_OUT_OF_MEMORY),
+				 errmsg("could not create ShmemIndex entry for data structure \"%s\"",
+						name)));
+	}
+
+	/*
+	 * We inserted the entry to the shared memory index.  Allocate requested
+	 * amount of shared memory for it, and initialize the index entry.
+	 */
+	structPtr = ShmemAllocRaw(request->options->size, &allocated_size);
+	if (structPtr == NULL)
+	{
+		/* out of memory; remove the failed ShmemIndex entry */
+		hash_search(ShmemIndex, name, HASH_REMOVE, NULL);
+		ereport(ERROR,
+				(errcode(ERRCODE_OUT_OF_MEMORY),
+				 errmsg("not enough shared memory for data structure"
+						" \"%s\" (%zu bytes requested)",
+						name, request->options->size)));
+	}
+	index_entry->size = request->options->size;
+	index_entry->allocated_size = allocated_size;
+	index_entry->location = structPtr;
+
+	/* Initialize depending on the kind of shmem area it is */
+	switch (request->kind)
+	{
+		case SHMEM_KIND_STRUCT:
+			if (request->options->ptr)
+				*(request->options->ptr) = index_entry->location;
+			break;
+		case SHMEM_KIND_HASH:
+			shmem_hash_init(structPtr, request->options);
+			break;
+	}
+}
+
+/*
+ * Look up a named shmem area in the shared memory index and attach to it.
+ *
+ * Note that this only performs the basic attachment actions depending on
+ * ShmemRequestKind, like setting the global pointer variable to the area for
+ * SHMEM_KIND_STRUCT or setting up the backend-private HTAB control struct.
+ * This does *not* call the subsystem-specific attach callbacks.  That's done
+ * later after all the shmem areas have been initialized or attached to.
+ */
+static bool
+AttachShmemIndexEntry(ShmemRequest *request, bool missing_ok)
+{
+	const char *name = request->options->name;
+	ShmemIndexEnt *index_entry;
+
+	/* look it up in the shmem index */
+	index_entry = (ShmemIndexEnt *)
+		hash_search(ShmemIndex, name, HASH_FIND, NULL);
+	if (!index_entry)
+	{
+		if (!missing_ok)
+			ereport(ERROR,
+					(errmsg("could not find ShmemIndex entry for data structure \"%s\"",
+							request->options->name)));
+		return false;
+	}
+
+	/* Check that the size in the index matches the request. */
+	if (index_entry->size != request->options->size &&
+		request->options->size != SHMEM_ATTACH_UNKNOWN_SIZE)
+	{
+		ereport(ERROR,
+				(errmsg("shared memory struct \"%s\" was created with" \
+						" different size: existing %zu, requested %zu",
+						name, index_entry->size, request->options->size)));
+	}
+
+	/*
+	 * Re-establish the caller's pointer variable, or do other actions to
+	 * attach depending on the kind of shmem area it is.
+	 */
+	switch (request->kind)
+	{
+		case SHMEM_KIND_STRUCT:
+			if (request->options->ptr)
+				*(request->options->ptr) = index_entry->location;
+			break;
+		case SHMEM_KIND_HASH:
+			shmem_hash_attach(index_entry->location, request->options);
+			break;
+	}
+
+	return true;
+}
 
 /*
  *	InitShmemAllocator() --- set up basic pointers to shared memory.
  *
  * Called at postmaster or stand-alone backend startup, to initialize the
  * allocator's data structure in the shared memory segment.  In EXEC_BACKEND,
- * this is also called at backend startup, to set up pointers to the shared
- * memory areas.
+ * this is also called at backend startup, to set up pointers to the
+ * already-initialized data structure.
  */
 void
 InitShmemAllocator(PGShmemHeader *seghdr)
 {
 	Size		offset;
+	int64		hash_nelems;
 	HASHCTL		info;
 	int			hash_flags;
 
@@ -142,6 +628,16 @@ InitShmemAllocator(PGShmemHeader *seghdr)
 #endif
 	Assert(seghdr != NULL);
 
+	if (IsUnderPostmaster)
+	{
+		Assert(shmem_request_state == SRS_INITIAL);
+	}
+	else
+	{
+		Assert(shmem_request_state == SRS_REQUESTING);
+		shmem_request_state = SRS_INITIALIZING;
+	}
+
 	/*
 	 * We assume the pointer and offset are MAXALIGN.  Not a hard requirement,
 	 * but it's true today and keeps the math below simpler.
@@ -186,19 +682,21 @@ InitShmemAllocator(PGShmemHeader *seghdr)
 	 * use ShmemInitHash() here because it relies on ShmemIndex being already
 	 * initialized.
 	 */
+	hash_nelems = list_length(pending_shmem_requests) + SHMEM_INDEX_ADDITIONAL_SIZE;
+
 	info.keysize = SHMEM_INDEX_KEYSIZE;
 	info.entrysize = sizeof(ShmemIndexEnt);
 	hash_flags = HASH_ELEM | HASH_STRINGS | HASH_FIXED_SIZE;
 
 	if (!IsUnderPostmaster)
 	{
-		ShmemAllocator->index_size = hash_estimate_size(SHMEM_INDEX_SIZE, info.entrysize);
+		ShmemAllocator->index_size = hash_estimate_size(hash_nelems, info.entrysize);
 		ShmemAllocator->index = (HASHHDR *) ShmemAlloc(ShmemAllocator->index_size);
 	}
 	ShmemIndex = shmem_hash_create(ShmemAllocator->index,
 								   ShmemAllocator->index_size,
 								   IsUnderPostmaster,
-								   "ShmemIndex", SHMEM_INDEX_SIZE,
+								   "ShmemIndex", hash_nelems,
 								   &info, hash_flags);
 	Assert(ShmemIndex != NULL);
 
@@ -219,6 +717,23 @@ InitShmemAllocator(PGShmemHeader *seghdr)
 	}
 }
 
+/*
+ * Reset state on postmaster crash restart.
+ */
+void
+ResetShmemAllocator(void)
+{
+	Assert(!IsUnderPostmaster);
+	shmem_request_state = SRS_INITIAL;
+
+	pending_shmem_requests = NIL;
+
+	/*
+	 * Note that we don't clear the registered callbacks.  We will need to
+	 * call them again as we restart
+	 */
+}
+
 /*
  * ShmemAlloc -- allocate max-aligned chunk from shared memory
  *
@@ -316,92 +831,191 @@ ShmemAddrIsValid(const void *addr)
 }
 
 /*
- * ShmemInitStruct -- Create/attach to a structure in shared memory.
+ * Register callbacks that define a shared memory area (or multiple areas).
  *
- *		This is called during initialization to find or allocate
- *		a data structure in shared memory.  If no other process
- *		has created the structure, this routine allocates space
- *		for it.  If it exists already, a pointer to the existing
- *		structure is returned.
+ * The system will call the callbacks at different stages of postmaster or
+ * backend startup, to allocate and initialize the area.
  *
- *	Returns: pointer to the object.  *foundPtr is set true if the object was
- *		already in the shmem index (hence, already initialized).
+ * This is normally called early during postmaster startup, but if the
+ * SHMEM_CALLBACKS_ALLOW_AFTER_STARTUP is set, this can also be used after
+ * startup, although after startup there's no guarantee that there's enough
+ * shared memory available.  When called after startup, this immediately calls
+ * the right callbacks depending on whether another backend had already
+ * initialized the area.
  *
- *	Note: before Postgres 9.0, this function returned NULL for some failure
- *	cases.  Now, it always throws error instead, so callers need not check
- *	for NULL.
+ * Note: In EXEC_BACKEND mode, this needs to be called in every backend
+ * process.  That's needed because we cannot pass down the callback function
+ * pointers from the postmaster process, because different processes may have
+ * loaded libraries to different addresses.
  */
-void *
-ShmemInitStruct(const char *name, Size size, bool *foundPtr)
+void
+RegisterShmemCallbacks(const ShmemCallbacks *callbacks)
 {
-	ShmemIndexEnt *result;
-	void	   *structPtr;
+	if (shmem_request_state == SRS_DONE && IsUnderPostmaster)
+	{
+		/*
+		 * After-startup initialization or attachment.  Call the appropriate
+		 * callbacks immmediately.
+		 */
+		if ((callbacks->flags & SHMEM_CALLBACKS_ALLOW_AFTER_STARTUP) == 0)
+			elog(ERROR, "cannot request shared memory at this time");
 
-	Assert(ShmemIndex != NULL);
+		CallShmemCallbacksAfterStartup(callbacks);
+	}
+	else
+	{
+		/* Remember the callbacks for later */
+		registered_shmem_callbacks = lappend(registered_shmem_callbacks,
+											 (void *) callbacks);
+	}
+}
+
+/*
+ * Register a shmem area (or multiple areas) after startup.
+ */
+static void
+CallShmemCallbacksAfterStartup(const ShmemCallbacks *callbacks)
+{
+	bool		found_any;
+	bool		notfound_any;
+
+	Assert(shmem_request_state == SRS_DONE);
+	shmem_request_state = SRS_REQUESTING;
+
+	/*
+	 * Call the request callback first. The callback make ShmemRequest*()
+	 * calls for each shmem area, adding them to pending_shmem_requests.
+	 */
+	Assert(pending_shmem_requests == NIL);
+	if (callbacks->request_fn)
+		callbacks->request_fn(callbacks->request_fn_arg);
+	shmem_request_state = SRS_AFTER_STARTUP_ATTACH_OR_INIT;
+
+	if (pending_shmem_requests == NIL)
+	{
+		shmem_request_state = SRS_DONE;
+		return;
+	}
 
+	/* Hold ShmemIndexLock while we allocate all the shmem entries */
 	LWLockAcquire(ShmemIndexLock, LW_EXCLUSIVE);
 
-	/* look it up in the shmem index */
-	result = (ShmemIndexEnt *)
-		hash_search(ShmemIndex, name, HASH_ENTER_NULL, foundPtr);
+	/*
+	 * Check if the requested shared memory areas have already been
+	 * initialized.  We assume all the areas requested by the request callback
+	 * to form a coherent unit such that they're all already initialized or
+	 * none.  Otherwise it would be ambiguous which callback, init or attach,
+	 * to callback afterwards.
+	 */
+	found_any = notfound_any = false;
+	foreach_ptr(ShmemRequest, request, pending_shmem_requests)
+	{
+		if (hash_search(ShmemIndex, request->options->name, HASH_FIND, NULL))
+			found_any = true;
+		else
+			notfound_any = true;
+	}
+	if (found_any && notfound_any)
+		elog(ERROR, "found some but not all");
 
-	if (!result)
+	/*
+	 * Allocate or attach all the shmem areas requested by the request_fn
+	 * callback.
+	 */
+	foreach_ptr(ShmemRequest, request, pending_shmem_requests)
 	{
-		LWLockRelease(ShmemIndexLock);
-		ereport(ERROR,
-				(errcode(ERRCODE_OUT_OF_MEMORY),
-				 errmsg("could not create ShmemIndex entry for data structure \"%s\"",
-						name)));
+		if (found_any)
+			AttachShmemIndexEntry(request, false);
+		else
+			InitShmemIndexEntry(request);
 	}
+	list_free_deep(pending_shmem_requests);
+	pending_shmem_requests = NIL;
 
-	if (*foundPtr)
+	/* Finish by calling the appropriate subsystem-specific callback */
+	if (found_any)
 	{
-		/*
-		 * Structure is in the shmem index so someone else has allocated it
-		 * already.  The size better be the same as the size we are trying to
-		 * initialize to, or there is a name conflict (or worse).
-		 */
-		if (result->size != size)
-		{
-			LWLockRelease(ShmemIndexLock);
-			ereport(ERROR,
-					(errmsg("ShmemIndex entry size is wrong for data structure"
-							" \"%s\": expected %zu, actual %zu",
-							name, size, result->size)));
-		}
-		structPtr = result->location;
+		if (callbacks->attach_fn)
+			callbacks->attach_fn(callbacks->attach_fn_arg);
 	}
 	else
 	{
-		Size		allocated_size;
-
-		/* It isn't in the table yet. allocate and initialize it */
-		structPtr = ShmemAllocRaw(size, &allocated_size);
-		if (structPtr == NULL)
-		{
-			/* out of memory; remove the failed ShmemIndex entry */
-			hash_search(ShmemIndex, name, HASH_REMOVE, NULL);
-			LWLockRelease(ShmemIndexLock);
-			ereport(ERROR,
-					(errcode(ERRCODE_OUT_OF_MEMORY),
-					 errmsg("not enough shared memory for data structure"
-							" \"%s\" (%zu bytes requested)",
-							name, size)));
-		}
-		result->size = size;
-		result->allocated_size = allocated_size;
-		result->location = structPtr;
+		if (callbacks->init_fn)
+			callbacks->init_fn(callbacks->init_fn_arg);
 	}
 
 	LWLockRelease(ShmemIndexLock);
+	shmem_request_state = SRS_DONE;
+}
 
-	Assert(ShmemAddrIsValid(structPtr));
+/*
+ * Call all shmem request callbacks.
+ */
+void
+ShmemCallRequestCallbacks(void)
+{
+	ListCell   *lc;
 
-	Assert(structPtr == (void *) CACHELINEALIGN(structPtr));
+	Assert(shmem_request_state == SRS_INITIAL);
+	shmem_request_state = SRS_REQUESTING;
+
+	foreach(lc, registered_shmem_callbacks)
+	{
+		const ShmemCallbacks *callbacks = (const ShmemCallbacks *) lfirst(lc);
 
-	return structPtr;
+		if (callbacks->request_fn)
+			callbacks->request_fn(callbacks->request_fn_arg);
+	}
 }
 
+/*
+ * ShmemInitStruct -- Create/attach to a structure in shared memory.
+ *
+ *		This is called during initialization to find or allocate
+ *		a data structure in shared memory.  If no other process
+ *		has created the structure, this routine allocates space
+ *		for it.  If it exists already, a pointer to the existing
+ *		structure is returned.
+ *
+ *	Returns: pointer to the object.  *foundPtr is set true if the object was
+ *		already in the shmem index (hence, already initialized).
+ *
+ * Note: This is a legacy interface, kept for backwards compatibility with
+ * extensions.  Use ShmemRequestStruct() in new code!
+ */
+void *
+ShmemInitStruct(const char *name, Size size, bool *foundPtr)
+{
+	void	   *ptr = NULL;
+	ShmemStructOpts options = {
+		.name = name,
+		.size = size,
+		.ptr = &ptr,
+	};
+	ShmemRequest request = {&options, SHMEM_KIND_STRUCT};
+
+	Assert(shmem_request_state == SRS_DONE ||
+		   shmem_request_state == SRS_INITIALIZING ||
+		   shmem_request_state == SRS_REQUESTING);
+
+	LWLockAcquire(ShmemIndexLock, LW_EXCLUSIVE);
+
+	/*
+	 * During postmaster startup, look up the existing entry if any.
+	 */
+	*foundPtr = false;
+	if (IsUnderPostmaster)
+		*foundPtr = AttachShmemIndexEntry(&request, true);
+
+	/* Initialize it if not found */
+	if (!*foundPtr)
+		InitShmemIndexEntry(&request);
+
+	LWLockRelease(ShmemIndexLock);
+
+	Assert(ptr != NULL);
+	return ptr;
+}
 
 /*
  * Add two Size values, checking for overflow
diff --git a/src/backend/storage/ipc/shmem_hash.c b/src/backend/storage/ipc/shmem_hash.c
index 0b05730129e..ab30461f247 100644
--- a/src/backend/storage/ipc/shmem_hash.c
+++ b/src/backend/storage/ipc/shmem_hash.c
@@ -21,9 +21,81 @@
 #include "postgres.h"
 
 #include "storage/shmem.h"
+#include "storage/shmem_internal.h"
+#include "utils/memutils.h"
+
+/*
+ * A very simple allocator used to carve out different parts of a hash table
+ * from a previously allocated contiguous shared memory area.
+ */
+typedef struct shmem_hash_allocator
+{
+	char	   *next;			/* start of free space in the area */
+	char	   *end;			/* end of the shmem area */
+} shmem_hash_allocator;
 
 static void *ShmemHashAlloc(Size size, void *alloc_arg);
 
+/*
+ * ShmemRequestHash -- Request a shared memory hash table.
+ *
+ * Similar to ShmemRequestStruct(), but requests a hash table instead of an
+ * opaque area.
+ */
+void
+ShmemRequestHashWithOpts(const ShmemHashOpts *options)
+{
+	ShmemHashOpts *options_copy;
+
+	Assert(options->name != NULL);
+
+	options_copy = MemoryContextAlloc(TopMemoryContext,
+									  sizeof(ShmemHashOpts));
+	memcpy(options_copy, options, sizeof(ShmemHashOpts));
+
+	/* Set options for the fixed-size area holding the hash table */
+	options_copy->base.name = options->name;
+	options_copy->base.size = hash_estimate_size(options_copy->nelems,
+												 options_copy->hash_info.entrysize);
+
+	ShmemRequestInternal(&options_copy->base, SHMEM_KIND_HASH);
+}
+
+void
+shmem_hash_init(void *location, ShmemStructOpts *base_options)
+{
+	ShmemHashOpts *options = (ShmemHashOpts *) base_options;
+	int			hash_flags = options->hash_flags;
+	HTAB	   *htab;
+
+	options->hash_info.hctl = location;
+	htab = shmem_hash_create(location, options->base.size, false,
+							 options->name,
+							 options->nelems, &options->hash_info, hash_flags);
+
+	if (options->ptr)
+		*options->ptr = htab;
+}
+
+void
+shmem_hash_attach(void *location, ShmemStructOpts *base_options)
+{
+	ShmemHashOpts *options = (ShmemHashOpts *) base_options;
+	int			hash_flags = options->hash_flags;
+	HTAB	   *htab;
+
+	/* attach to it rather than allocate and initialize new space */
+	hash_flags |= HASH_ATTACH;
+	options->hash_info.hctl = location;
+	Assert(options->hash_info.hctl != NULL);
+	htab = shmem_hash_create(location, options->base.size, true,
+							 options->name,
+							 options->nelems, &options->hash_info, hash_flags);
+
+	if (options->ptr)
+		*options->ptr = htab;
+}
+
 /*
  * ShmemInitHash -- Create and initialize, or attach to, a
  *		shared memory hash table.
@@ -40,9 +112,8 @@ static void *ShmemHashAlloc(Size size, void *alloc_arg);
  * to shared-memory hash tables are added here, except that callers may
  * choose to specify HASH_PARTITION.
  *
- * Note: before Postgres 9.0, this function returned NULL for some failure
- * cases.  Now, it always throws error instead, so callers need not check
- * for NULL.
+ * Note: This is a legacy interface, kept for backwards compatibility with
+ * extensions.  Use ShmemRequestHash() in new code!
  */
 HTAB *
 ShmemInitHash(const char *name,		/* table string name for shmem index */
@@ -56,7 +127,14 @@ ShmemInitHash(const char *name,		/* table string name for shmem index */
 
 	size = hash_estimate_size(nelems, infoP->entrysize);
 
-	/* look it up in the shmem index or allocate */
+	/*
+	 * Look it up in the shmem index or allocate.
+	 *
+	 * NOTE: The area is requested internally as SHMEM_KIND_STRUCT instead of
+	 * SHMEM_KIND_HASH.  That's correct because we do the hash table
+	 * initialization by calling shmem_hash_create() ourselves.  (We don't
+	 * expose the request kind to users; if we did, that would be confusing.)
+	 */
 	location = ShmemInitStruct(name, size, &found);
 
 	return shmem_hash_create(location, size, found,
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 5c47cf13473..9b880a6af65 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -121,6 +121,9 @@ FastPathLockShmemSize(void)
 
 	size = add_size(size, mul_size(TotalProcs, (fpLockBitsSize + fpRelIdSize)));
 
+	Assert(TotalProcs > 0);
+	Assert(size > 0);
+
 	return size;
 }
 
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 10be60011ad..93851269e43 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -67,6 +67,7 @@
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
 #include "storage/procsignal.h"
+#include "storage/shmem_internal.h"
 #include "storage/sinval.h"
 #include "storage/standby.h"
 #include "tcop/backend_startup.h"
@@ -4155,7 +4156,14 @@ PostgresSingleUserMain(int argc, char *argv[],
 	InitializeFastPathLocks();
 
 	/*
-	 * Give preloaded libraries a chance to request additional shared memory.
+	 * Before computing the total size needed, give all subsystems, including
+	 * add-ins, a chance to chance to adjust their requested shmem sizes.
+	 */
+	ShmemCallRequestCallbacks();
+
+	/*
+	 * Also call any legacy shmem request hooks that might'be been installed
+	 * by preloaded libraries.
 	 */
 	process_shmem_requests();
 
diff --git a/src/include/storage/shmem.h b/src/include/storage/shmem.h
index 82f5403c952..147a6915f7e 100644
--- a/src/include/storage/shmem.h
+++ b/src/include/storage/shmem.h
@@ -3,6 +3,11 @@
  * shmem.h
  *	  shared memory management structures
  *
+ * This file contains public functions for other core subsystems and
+ * extensions to allocate shared memory.  Internal functions for the shmem
+ * allocator itself and hooking it to the rest of the system are in
+ * shmem_internal.h
+ *
  * Historical note:
  * A long time ago, Postgres' shared memory region was allowed to be mapped
  * at a different address in each process, and shared memory "pointers" were
@@ -23,43 +28,165 @@
 
 #include "utils/hsearch.h"
 
+/*
+ * Options for ShmemRequestStruct()
+ *
+ * 'name' and 'size' are required.  Initialize any optional fields that you
+ * don't use to zeros.
+ *
+ * After registration, the shmem machinery reserves memory for the area, sets
+ * '*ptr' to point to the allocation, and calls the callbacks at the right
+ * moments.
+ */
+typedef struct ShmemStructOpts
+{
+	const char *name;
 
-/* shmem.c */
-typedef struct PGShmemHeader PGShmemHeader; /* avoid including
-											 * storage/pg_shmem.h here */
-extern void InitShmemAllocator(PGShmemHeader *seghdr);
-extern void *ShmemAlloc(Size size);
-extern void *ShmemAllocNoError(Size size);
-extern void *ShmemHashAlloc(Size size, void *alloc_arg);
+	/*
+	 * Requested size of the shmem allocation.
+	 *
+	 * When attaching to an existing allocation, the size must match the size
+	 * given when the shmem region was allocated.  This cross-check can be
+	 * disabled specifying SHMEM_ATTACH_UNKNOWN_SIZE.
+	 */
+	ssize_t		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
+	 * the shared memory area later.  *ptr is set before the init_fn or
+	 * attach_fn callback is called.
+	 */
+	void	  **ptr;
+} ShmemStructOpts;
+
+#define SHMEM_ATTACH_UNKNOWN_SIZE (-1)
+
+/*
+ * Options for ShmemRequestHash()
+ *
+ * Each hash table is backed by an allocated area, but if 'max_size' is
+ * greater than 'init_size', it can also grow beyond the initial allocated
+ * area by allocating more hash entries from the global unreserved space.
+ */
+typedef struct ShmemHashOpts
+{
+	ShmemStructOpts base;
+
+	/*
+	 * Name of the shared memory area.  Required.  Must be unique across the
+	 * system.
+	 */
+	const char *name;
+
+	/*
+	 * 'nelems' is the max number of elements for the hash table.
+	 */
+	int64		nelems;
+
+	/*
+	 * Hash table options passed to hash_create()
+	 *
+	 * hash_info and hash_flags must specify at least the entry sizes and key
+	 * comparison semantics (see hash_create()).  Flag bits and values
+	 * specific to shared-memory hash tables are added implicitly in
+	 * ShmemRequestHash(), except that callers may choose to specify
+	 * HASH_PARTITION and/or HASH_FIXED_SIZE.
+	 */
+	HASHCTL		hash_info;
+	int			hash_flags;
+
+	/*
+	 * When the hash table is initialized or attached to, pointer to its
+	 * backend-private handle is stored in *ptr.  It usually points to a
+	 * global variable, used to access the hash table later.
+	 */
+	HTAB	  **ptr;
+} ShmemHashOpts;
+
+typedef void (*ShmemRequestCallback) (void *arg);
+typedef void (*ShmemInitCallback) (void *arg);
+typedef void (*ShmemAttachCallback) (void *arg);
+
+/*
+ * Shared memory is reserved and allocated in stages at postmaster startup,
+ * and in EXEC_BACKEND mode, there's some extra work done to "attach" to them
+ * at backend startup.  ShmemCallbacks holds callback functions that are
+ * called at different stages.
+ */
+typedef struct ShmemCallbacks
+{
+	/* SHMEM_CALLBACKS_* flags */
+	int			flags;
+
+	/*
+	 * 'request_fn' is called during postmaster startup, before the shared
+	 * memory has been allocated.  The function should call
+	 * RequestShmemStruct() and RequestShmemHash() to register the subsystem's
+	 * shared memory needs.
+	 */
+	ShmemRequestCallback request_fn;
+	void	   *request_fn_arg;
+
+	/*
+	 * Initialization callback function.  This is called when the shared
+	 * memory area is allocated, usually at postmaster startup.
+	 */
+	ShmemInitCallback init_fn;
+	void	   *init_fn_arg;
+
+	/*
+	 * Attachment callback function.  In EXEC_BACKEND mode, this is called at
+	 * startup of each backend.  In !EXEC_BACKEND mode, this is only called if
+	 * the shared memory area is registered after postmaster startup (see
+	 * SHMEM_CALLBACKS_ALLOW_AFTER_STARTUP).
+	 */
+	ShmemAttachCallback attach_fn;
+	void	   *attach_fn_arg;
+} ShmemCallbacks;
+
+/*
+ * Flags to control the behavior of RegisterShmemCallbacks().
+ *
+ * ALLOW_AFTER_STARTUP: Allow these shared memory usages to be registered
+ * after postmaster startup.  Normally, registering a shared memory system
+ * after postmaster startup is not allowed e.g. in an add-in library loaded
+ * on-demaind in a backend.  If a subsystem sets this flag, the callbacks are
+ * called immediately after registration, to initialize or attach to the
+ * requested shared memory areas.  This is not used by any built-in
+ * subsystems, but extensions may find it useful.
+ */
+#define SHMEM_CALLBACKS_ALLOW_AFTER_STARTUP		0x00000001
+
+extern void RegisterShmemCallbacks(const ShmemCallbacks *callbacks);
 extern bool ShmemAddrIsValid(const void *addr);
+
+/*
+ * These macros provide syntactic sugar for calling the underlying functions
+ * with named arguments -like syntax.
+ */
+#define ShmemRequestStruct(...)  \
+	ShmemRequestStructWithOpts(&(ShmemStructOpts){__VA_ARGS__})
+
+#define ShmemRequestHash(...)  \
+	ShmemRequestHashWithOpts(&(ShmemHashOpts){__VA_ARGS__})
+
+extern void ShmemRequestStructWithOpts(const ShmemStructOpts *options);
+extern void ShmemRequestHashWithOpts(const ShmemHashOpts *options);
+
+/* legacy shmem allocation functions */
 extern void *ShmemInitStruct(const char *name, Size size, bool *foundPtr);
+extern HTAB *ShmemInitHash(const char *name, int64 nelems,
+						   HASHCTL *infoP, int hash_flags);
+extern void *ShmemAlloc(Size size);
+extern void *ShmemAllocNoError(Size size);
+
 extern Size add_size(Size s1, Size s2);
 extern Size mul_size(Size s1, Size s2);
 
 extern PGDLLIMPORT Size pg_get_shmem_pagesize(void);
 
-/* shmem_hash.c */
-extern HTAB *ShmemInitHash(const char *name, int64 nelems,
-						   HASHCTL *infoP, int hash_flags);
-extern HTAB *shmem_hash_create(void *location, size_t size, bool found,
-							   const char *name, int64 nelems, HASHCTL *infoP, int hash_flags)
-
 /* ipci.c */
 extern void RequestAddinShmemSpace(Size size);
 
-/* size constants for the shmem index table */
- /* max size of data structure string name */
-#define SHMEM_INDEX_KEYSIZE		 (48)
- /* max number of named shmem structures and hash tables */
-#define SHMEM_INDEX_SIZE		 (256)
-
-/* this is a hash bucket in the shmem index table */
-typedef struct
-{
-	char		key[SHMEM_INDEX_KEYSIZE];	/* string name */
-	void	   *location;		/* location in shared mem */
-	Size		size;			/* # bytes requested for the structure */
-	Size		allocated_size; /* # bytes actually allocated */
-} ShmemIndexEnt;
-
 #endif							/* SHMEM_H */
diff --git a/src/include/storage/shmem_internal.h b/src/include/storage/shmem_internal.h
new file mode 100644
index 00000000000..fe12bf33439
--- /dev/null
+++ b/src/include/storage/shmem_internal.h
@@ -0,0 +1,52 @@
+/*-------------------------------------------------------------------------
+ *
+ * shmem_internal.h
+ *	  Internal functions related to shmem allocation
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/storage/shmem_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef SHMEM_INTERNAL_H
+#define SHMEM_INTERNAL_H
+
+#include "storage/shmem.h"
+#include "utils/hsearch.h"
+
+/* Different kinds of shmem areas. */
+typedef enum
+{
+	SHMEM_KIND_STRUCT = 0,		/* plain, contiguous area of memory */
+	SHMEM_KIND_HASH,			/* a hash table */
+} ShmemRequestKind;
+
+/* shmem.c */
+typedef struct PGShmemHeader PGShmemHeader; /* avoid including
+											 * storage/pg_shmem.h here */
+extern void ShmemCallRequestCallbacks(void);
+extern void InitShmemAllocator(PGShmemHeader *seghdr);
+#ifdef EXEC_BACKEND
+extern void AttachShmemAllocator(PGShmemHeader *seghdr);
+#endif
+extern void ResetShmemAllocator(void);
+
+extern void ShmemRequestInternal(ShmemStructOpts *options, ShmemRequestKind kind);
+
+extern size_t ShmemGetRequestedSize(void);
+extern void ShmemInitRequested(void);
+#ifdef EXEC_BACKEND
+extern void ShmemAttachRequested(void);
+#endif
+
+extern PGDLLIMPORT Size pg_get_shmem_pagesize(void);
+
+/* shmem_hash.c */
+extern HTAB *shmem_hash_create(void *location, size_t size, bool found,
+							   const char *name, int64 nelems, HASHCTL *infoP, int hash_flags);
+extern void shmem_hash_init(void *location, ShmemStructOpts *options);
+extern void shmem_hash_attach(void *location, ShmemStructOpts *options);
+
+#endif							/* SHMEM_INTERNAL_H */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index c72f6c59573..b84167741fb 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2863,9 +2863,16 @@ SharedTypmodTableEntry
 Sharedsort
 ShellTypeInfo
 ShippableCacheEntry
-ShmemAllocatorData
 ShippableCacheKey
+ShmemAllocatorData
+ShmemCallbacks
 ShmemIndexEnt
+ShmemHashDesc
+ShmemHashOpts
+ShmemRequest
+ShmemRequestKind
+ShmemStructDesc
+ShmemStructOpts
 ShutdownForeignScan_function
 ShutdownInformation
 ShutdownMode
-- 
2.34.1



  [text/x-patch] v20260405-0003-Add-test-module-to-test-after-startup-shme.patch (10.1K, 4-v20260405-0003-Add-test-module-to-test-after-startup-shme.patch)
  download | inline diff:
From b185ee67f0e9108edb418edeb14d05da21f6a689 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Wed, 1 Apr 2026 18:10:31 +0300
Subject: [PATCH v20260405 03/15] Add test module to test after-startup shmem
 allocations

None of the existing modules could make use of the lazy shmem
allocation after postmaster startup:

- pg_stat_statements needs to load and dump stats file on startup and
  shutdown, which doesn't really work if the library is not loaded into
  postmaster

- test_aio registers injection points, which reference the library
  itself, which creates a weird initialization loop if you try to do
  that directly from _PG_init() in a backend. The initialization
  really needs to happen after _PG_init()

- injection_points would be a candidate, but it already knows to use
  DSM when it's not loaded from shared_preload_libraries.
---
 src/test/modules/Makefile                     |   1 +
 src/test/modules/meson.build                  |   1 +
 src/test/modules/test_shmem/Makefile          |  24 +++++
 src/test/modules/test_shmem/meson.build       |  33 ++++++
 .../test_shmem/t/001_late_shmem_alloc.pl      |  49 +++++++++
 .../modules/test_shmem/test_shmem--1.0.sql    |   9 ++
 src/test/modules/test_shmem/test_shmem.c      | 101 ++++++++++++++++++
 .../modules/test_shmem/test_shmem.control     |   3 +
 src/tools/pgindent/typedefs.list              |   1 +
 9 files changed, 222 insertions(+)
 create mode 100644 src/test/modules/test_shmem/Makefile
 create mode 100644 src/test/modules/test_shmem/meson.build
 create mode 100644 src/test/modules/test_shmem/t/001_late_shmem_alloc.pl
 create mode 100644 src/test/modules/test_shmem/test_shmem--1.0.sql
 create mode 100644 src/test/modules/test_shmem/test_shmem.c
 create mode 100644 src/test/modules/test_shmem/test_shmem.control

diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 864b407abcf..f1b04c99969 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -48,6 +48,7 @@ SUBDIRS = \
 		  test_resowner \
 		  test_rls_hooks \
 		  test_saslprep \
+		  test_shmem \
 		  test_shm_mq \
 		  test_slru \
 		  test_tidstore \
diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build
index e5acacd5083..fc99552d9ab 100644
--- a/src/test/modules/meson.build
+++ b/src/test/modules/meson.build
@@ -49,6 +49,7 @@ subdir('test_regex')
 subdir('test_resowner')
 subdir('test_rls_hooks')
 subdir('test_saslprep')
+subdir('test_shmem')
 subdir('test_shm_mq')
 subdir('test_slru')
 subdir('test_tidstore')
diff --git a/src/test/modules/test_shmem/Makefile b/src/test/modules/test_shmem/Makefile
new file mode 100644
index 00000000000..2407f7462fe
--- /dev/null
+++ b/src/test/modules/test_shmem/Makefile
@@ -0,0 +1,24 @@
+# src/test/modules/test_shmem/Makefile
+
+PGFILEDESC = "test_shmem - test code for shmem allocations"
+
+MODULE_big = test_shmem
+OBJS = \
+	$(WIN32RES) \
+	test_shmem.o
+
+EXTENSION = test_shmem
+DATA = test_shmem--1.0.sql
+
+TAP_TESTS = 1
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_shmem
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_shmem/meson.build b/src/test/modules/test_shmem/meson.build
new file mode 100644
index 00000000000..fb4bf328b8f
--- /dev/null
+++ b/src/test/modules/test_shmem/meson.build
@@ -0,0 +1,33 @@
+# Copyright (c) 2024-2026, PostgreSQL Global Development Group
+
+test_shmem_sources = files(
+  'test_shmem.c',
+)
+
+if host_system == 'windows'
+  test_shmem_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_shmem',
+    '--FILEDESC', 'test_shmem - test code for shmem allocations',])
+endif
+
+test_shmem = shared_module('test_shmem',
+  test_shmem_sources,
+  kwargs: pg_test_mod_args,
+)
+test_install_libs += test_shmem
+
+test_install_data += files(
+  'test_shmem.control',
+  'test_shmem--1.0.sql',
+)
+
+tests += {
+  'name': 'test_shmem',
+  'sd': meson.current_source_dir(),
+  'bd': meson.current_build_dir(),
+  'tap': {
+    'tests': [
+      't/001_late_shmem_alloc.pl',
+    ],
+  },
+}
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
new file mode 100644
index 00000000000..c154f57682a
--- /dev/null
+++ b/src/test/modules/test_shmem/t/001_late_shmem_alloc.pl
@@ -0,0 +1,49 @@
+# 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 allocating memory after startup, i.e. when the library is not
+# in shared_preload_libraries
+###
+my $node = PostgreSQL::Test::Cluster->new('main');
+$node->init;
+$node->start;
+
+
+$node->safe_psql("postgres", "CREATE EXTENSION test_shmem;");
+
+# Check that the attach counter is incremented on a new connection
+my $attach_count1 = $node->safe_psql("postgres", "SELECT get_test_shmem_attach_count();");
+my $attach_count2 = $node->safe_psql("postgres", "SELECT get_test_shmem_attach_count();");
+cmp_ok($attach_count2, '>', $attach_count1, "attach callback is called in each backend");
+$node->stop;
+
+###
+# Test that loading via shared_preload_libraries also works
+###
+$node->append_conf('postgresql.conf', "shared_preload_libraries = 'test_shmem'");
+$node->start;
+
+# When loaded via shared_preload_libraries, the attach callback is
+# called or not, depending on whether this is an EXEC_BACKEND build.
+my $exec_backend = $node->safe_psql("postgres", "SHOW debug_exec_backend;") eq 'on';
+$attach_count1 = $node->safe_psql("postgres", "SELECT get_test_shmem_attach_count();");
+$attach_count2 = $node->safe_psql("postgres", "SELECT get_test_shmem_attach_count();");
+
+if ($exec_backend)
+{
+   cmp_ok($attach_count2, '>', $attach_count1, "attach callback is called in each backend when loaded via shared_preload_libraries");
+}
+else
+{
+   ok($attach_count1 == 0 && $attach_count2 == 0, "attach callback is not called when loaded via shared_preload_libraries");
+}
+
+$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
new file mode 100644
index 00000000000..2d01fd9256c
--- /dev/null
+++ b/src/test/modules/test_shmem/test_shmem--1.0.sql
@@ -0,0 +1,9 @@
+/* src/test/modules/test_shmem/test_shmem--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_shmem" to load this file. \quit
+
+
+CREATE FUNCTION get_test_shmem_attach_count()
+RETURNS pg_catalog.int4 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
new file mode 100644
index 00000000000..9bd4012b435
--- /dev/null
+++ b/src/test/modules/test_shmem/test_shmem.c
@@ -0,0 +1,101 @@
+/*-------------------------------------------------------------------------
+ *
+ * test_shmem.c
+ *		Helpers to test shmem allocation routines
+ *
+ * Test basic memory allocation in an extension module. One notable feature
+ * that is not exercised by any other module in the repository is the
+ * allocating (non-DSM) shared memory after postmaster startup.
+ *
+ * Copyright (c) 2020-2026, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/test/modules/test_shmem/test_shmem.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "miscadmin.h"
+#include "storage/shmem.h"
+
+
+PG_MODULE_MAGIC;
+
+typedef struct TestShmemData
+{
+	int			value;
+	bool		initialized;
+	int			attach_count;
+} TestShmemData;
+
+static TestShmemData *TestShmem;
+
+static bool attached_or_initialized = false;
+
+static void test_shmem_request(void *arg);
+static void test_shmem_init(void *arg);
+static void test_shmem_attach(void *arg);
+
+static const ShmemCallbacks TestShmemCallbacks = {
+	.flags = SHMEM_CALLBACKS_ALLOW_AFTER_STARTUP,
+	.request_fn = test_shmem_request,
+	.init_fn = test_shmem_init,
+	.attach_fn = test_shmem_attach,
+};
+
+static void
+test_shmem_request(void *arg)
+{
+	elog(LOG, "test_shmem_request callback called");
+
+	ShmemRequestStruct(.name = "test_shmem area",
+					   .size = sizeof(TestShmemData),
+					   .ptr = (void **) &TestShmem);
+}
+
+static void
+test_shmem_init(void *arg)
+{
+	elog(LOG, "init callback called");
+	if (TestShmem->initialized)
+		elog(ERROR, "shmem area already initialized");
+	TestShmem->initialized = true;
+
+	if (attached_or_initialized)
+		elog(ERROR, "attach or initialize already called in this process");
+	attached_or_initialized = true;
+}
+
+static void
+test_shmem_attach(void *arg)
+{
+	elog(LOG, "test_shmem_attach callback called");
+	if (!TestShmem->initialized)
+		elog(ERROR, "shmem area not yet initialized");
+	TestShmem->attach_count++;
+
+	if (attached_or_initialized)
+		elog(ERROR, "attach or initialize already called in this process");
+	attached_or_initialized = true;
+}
+
+void
+_PG_init(void)
+{
+	elog(LOG, "test_shmem module's _PG_init called");
+	RegisterShmemCallbacks(&TestShmemCallbacks);
+}
+
+PG_FUNCTION_INFO_V1(get_test_shmem_attach_count);
+Datum
+get_test_shmem_attach_count(PG_FUNCTION_ARGS)
+{
+	if (!attached_or_initialized)
+		elog(ERROR, "shmem area not attached or initialized in this process");
+	if (!TestShmem->initialized)
+		elog(ERROR, "shmem area not yet initialized");
+	PG_RETURN_INT32(TestShmem->attach_count);
+}
diff --git a/src/test/modules/test_shmem/test_shmem.control b/src/test/modules/test_shmem/test_shmem.control
new file mode 100644
index 00000000000..f2f26f4537a
--- /dev/null
+++ b/src/test/modules/test_shmem/test_shmem.control
@@ -0,0 +1,3 @@
+comment = 'Test code for shmem allocations'
+default_version = '1.0'
+module_pathname = '$libdir/test_shmem'
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b84167741fb..63c0b3a9465 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3146,6 +3146,7 @@ TestDSMRegistryHashEntry
 TestDSMRegistryStruct
 TestDecodingData
 TestDecodingTxnData
+TestShmemData
 TestSpec
 TestValueType
 TextFreq
-- 
2.34.1



  [text/x-patch] v20260405-0005-Introduce-registry-of-built-in-subsystems.patch (7.3K, 5-v20260405-0005-Introduce-registry-of-built-in-subsystems.patch)
  download | inline diff:
From a2f03667d6dee4a12631d122dc96440a0472320e Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Wed, 1 Apr 2026 18:21:02 +0300
Subject: [PATCH v20260405 05/15] Introduce registry of built-in subsystems

To add a new built-in subsystem, add it to subsystemslist.h. That
hooks up its callbacks so that they get called at the right times
during postmaster startup. For now this is unused, but will replace
the current SubsystemShmemSize() and SubsystemShmemInit() calls in
the next commits.
---
 src/backend/bootstrap/bootstrap.c       |  2 ++
 src/backend/postmaster/launch_backend.c |  2 ++
 src/backend/postmaster/postmaster.c     |  5 +++++
 src/backend/storage/ipc/ipci.c          | 21 +++++++++++++++++
 src/backend/tcop/postgres.c             |  3 +++
 src/include/storage/ipc.h               |  1 +
 src/include/storage/subsystemlist.h     | 23 +++++++++++++++++++
 src/include/storage/subsystems.h        | 30 +++++++++++++++++++++++++
 src/tools/pginclude/headerscheck        |  1 +
 9 files changed, 88 insertions(+)
 create mode 100644 src/include/storage/subsystemlist.h
 create mode 100644 src/include/storage/subsystems.h

diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index c707ccfa563..49d88a1b6dd 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -363,6 +363,8 @@ BootstrapModeMain(int argc, char *argv[], bool check_only)
 	SetProcessingMode(BootstrapProcessing);
 	IgnoreSystemIndexes = true;
 
+	RegisterBuiltinShmemCallbacks();
+
 	InitializeMaxBackends();
 
 	/*
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 0973010b7dc..ed0f4f2d234 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -664,6 +664,8 @@ SubPostmasterMain(int argc, char *argv[])
 	 */
 	LocalProcessControlFile(false);
 
+	RegisterBuiltinShmemCallbacks();
+
 	/*
 	 * Reload any libraries that were preloaded by the postmaster.  Since we
 	 * exec'd this process, those libraries didn't come along with us; but we
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 693475014fe..b2010bce186 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -922,6 +922,11 @@ PostmasterMain(int argc, char *argv[])
 	 */
 	ApplyLauncherRegister();
 
+	/*
+	 * Register the shared memory needs of all core subsystems.
+	 */
+	RegisterBuiltinShmemCallbacks();
+
 	/*
 	 * process any libraries that should be preloaded at postmaster start
 	 */
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 24422a80ab3..e4a6a52f12d 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -52,6 +52,7 @@
 #include "storage/procsignal.h"
 #include "storage/shmem_internal.h"
 #include "storage/sinvaladt.h"
+#include "storage/subsystems.h"
 #include "utils/guc.h"
 #include "utils/injection_point.h"
 #include "utils/wait_event.h"
@@ -252,6 +253,26 @@ CreateSharedMemoryAndSemaphores(void)
 		shmem_startup_hook();
 }
 
+/*
+ * Early initialization of various subsystems, giving them a chance to
+ * register their shared memory needs before the shared memory segment is
+ * allocated.
+ */
+void
+RegisterBuiltinShmemCallbacks(void)
+{
+	/*
+	 * Call RegisterShmemCallbacks(...) on each subsystem listed in
+	 * subsystemslist.h
+	 */
+#define PG_SHMEM_SUBSYSTEM(subsystem_callbacks) \
+	RegisterShmemCallbacks(&(subsystem_callbacks));
+
+#include "storage/subsystemlist.h"
+
+#undef PG_SHMEM_SUBSYSTEM
+}
+
 /*
  * Initialize various subsystems, setting up their data structures in
  * shared memory.
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 93851269e43..6a9ff3ad225 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -4138,6 +4138,9 @@ PostgresSingleUserMain(int argc, char *argv[],
 	/* read control file (error checking and contains config ) */
 	LocalProcessControlFile(false);
 
+	/* Register the shared memory needs of all core subsystems. */
+	RegisterBuiltinShmemCallbacks();
+
 	/*
 	 * process any libraries that should be preloaded at postmaster start
 	 */
diff --git a/src/include/storage/ipc.h b/src/include/storage/ipc.h
index da32787ab51..b205b00e7a1 100644
--- a/src/include/storage/ipc.h
+++ b/src/include/storage/ipc.h
@@ -77,6 +77,7 @@ extern void check_on_shmem_exit_lists_are_empty(void);
 /* ipci.c */
 extern PGDLLIMPORT shmem_startup_hook_type shmem_startup_hook;
 
+extern void RegisterBuiltinShmemCallbacks(void);
 extern Size CalculateShmemSize(void);
 extern void CreateSharedMemoryAndSemaphores(void);
 #ifdef EXEC_BACKEND
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
new file mode 100644
index 00000000000..ed43c90bcc3
--- /dev/null
+++ b/src/include/storage/subsystemlist.h
@@ -0,0 +1,23 @@
+/*---------------------------------------------------------------------------
+ * subsystemlist.h
+ *
+ * List of initialization callbacks of built-in subsystems. This is kept in
+ * its own source file for possible use by automatic tools.
+ * PG_SHMEM_SUBSYSTEM is defined in the callers depending on how the list is
+ * used.
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/storage/subsystemlist.h
+ *---------------------------------------------------------------------------
+ */
+
+/* there is deliberately not an #ifndef SUBSYSTEMLIST_H here */
+
+/*
+ * Note: there are some inter-dependencies between these, so the order of some
+ * of these matter.
+ */
+
+/* TODO: empty for now */
diff --git a/src/include/storage/subsystems.h b/src/include/storage/subsystems.h
new file mode 100644
index 00000000000..38b735bec67
--- /dev/null
+++ b/src/include/storage/subsystems.h
@@ -0,0 +1,30 @@
+/*-------------------------------------------------------------------------
+ *
+ * subsystems.h
+ *	  Provide extern declarations for all the built-in subsystem callbacks
+ *
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/storage/subsystems.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef SUBSYSTEMS_H
+#define SUBSYSTEMS_H
+
+#include "storage/shmem.h"
+
+/*
+ * Extern declarations of all the built-in subsystem callbacks
+ *
+ * The actual list is in subsystemlist.h, so that the same list can be used
+ * for other purposes.
+ */
+#define PG_SHMEM_SUBSYSTEM(callbacks) \
+	extern const ShmemCallbacks callbacks;
+#include "storage/subsystemlist.h"
+#undef PG_SHMEM_SUBSYSTEM
+
+#endif							/* SUBSYSTEMS_H */
diff --git a/src/tools/pginclude/headerscheck b/src/tools/pginclude/headerscheck
index 14c466cc237..24f7416185e 100755
--- a/src/tools/pginclude/headerscheck
+++ b/src/tools/pginclude/headerscheck
@@ -131,6 +131,7 @@ do
 	test "$f" = src/include/postmaster/proctypelist.h && continue
 	test "$f" = src/include/regex/regerrs.h && continue
 	test "$f" = src/include/storage/lwlocklist.h && continue
+	test "$f" = src/include/storage/subsystemlist.h && continue
 	test "$f" = src/include/tcop/cmdtaglist.h && continue
 	test "$f" = src/interfaces/ecpg/preproc/c_kwlist.h && continue
 	test "$f" = src/interfaces/ecpg/preproc/ecpg_kwlist.h && continue
-- 
2.34.1



  [text/x-patch] v20260405-0004-Convert-pg_stat_statements-to-use-the-new-.patch (11.3K, 6-v20260405-0004-Convert-pg_stat_statements-to-use-the-new-.patch)
  download | inline diff:
From 36ec5df07fc74329a6cfc16252245b88e47b094c Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Wed, 1 Apr 2026 18:21:24 +0300
Subject: [PATCH v20260405 04/15] Convert pg_stat_statements to use the new
 interface

As part of this, embed the LWLock it needs in the shared memory struct
itself, so that we don't need to use RequestNamedLWLockTranche()
anymore. LWLockNewTrancheId+LWLockInitialize is more convenient to use
in extensions.

Reviewed-by: Ashutosh Bapat <[email protected]>
Reviewed-by: Zsolt Parragi <[email protected]>
Reviewed-by: Robert Haas <[email protected]>
Discussion: https://www.postgresql.org/message-id/CAExHW5vM1bneLYfg0wGeAa=52UiJ3z4vKd3AJ72X8Fw6k3KKrg@mail.gmail.com
---
 .../pg_stat_statements/pg_stat_statements.c   | 173 ++++++++----------
 1 file changed, 77 insertions(+), 96 deletions(-)

diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c
index 5494d41dca1..f078b4fe71b 100644
--- a/contrib/pg_stat_statements/pg_stat_statements.c
+++ b/contrib/pg_stat_statements/pg_stat_statements.c
@@ -249,7 +249,7 @@ typedef struct pgssEntry
  */
 typedef struct pgssSharedState
 {
-	LWLock	   *lock;			/* protects hashtable search/modification */
+	LWLockPadded lock;			/* protects hashtable search/modification */
 	double		cur_median_usage;	/* current median usage in hashtable */
 	Size		mean_query_len; /* current mean entry text length */
 	slock_t		mutex;			/* protects following fields only: */
@@ -259,14 +259,24 @@ typedef struct pgssSharedState
 	pgssGlobalStats stats;		/* global statistics for pgss */
 } pgssSharedState;
 
+/* Links to shared memory state */
+static pgssSharedState *pgss;
+static HTAB *pgss_hash;
+
+static void pgss_shmem_request(void *arg);
+static void pgss_shmem_init(void *arg);
+
+static const ShmemCallbacks pgss_shmem_callbacks = {
+	.request_fn = pgss_shmem_request,
+	.init_fn = pgss_shmem_init,
+};
+
 /*---- Local variables ----*/
 
 /* Current nesting depth of planner/ExecutorRun/ProcessUtility calls */
 static int	nesting_level = 0;
 
 /* Saved hook values */
-static shmem_request_hook_type prev_shmem_request_hook = NULL;
-static shmem_startup_hook_type prev_shmem_startup_hook = NULL;
 static post_parse_analyze_hook_type prev_post_parse_analyze_hook = NULL;
 static planner_hook_type prev_planner_hook = NULL;
 static ExecutorStart_hook_type prev_ExecutorStart = NULL;
@@ -275,10 +285,6 @@ static ExecutorFinish_hook_type prev_ExecutorFinish = NULL;
 static ExecutorEnd_hook_type prev_ExecutorEnd = NULL;
 static ProcessUtility_hook_type prev_ProcessUtility = NULL;
 
-/* Links to shared memory state */
-static pgssSharedState *pgss = NULL;
-static HTAB *pgss_hash = NULL;
-
 /*---- GUC variables ----*/
 
 typedef enum
@@ -331,8 +337,6 @@ PG_FUNCTION_INFO_V1(pg_stat_statements_1_13);
 PG_FUNCTION_INFO_V1(pg_stat_statements);
 PG_FUNCTION_INFO_V1(pg_stat_statements_info);
 
-static void pgss_shmem_request(void);
-static void pgss_shmem_startup(void);
 static void pgss_shmem_shutdown(int code, Datum arg);
 static void pgss_post_parse_analyze(ParseState *pstate, Query *query,
 									JumbleState *jstate);
@@ -366,7 +370,6 @@ static void pgss_store(const char *query, int64 queryId,
 static void pg_stat_statements_internal(FunctionCallInfo fcinfo,
 										pgssVersion api_version,
 										bool showtext);
-static Size pgss_memsize(void);
 static pgssEntry *entry_alloc(pgssHashKey *key, Size query_offset, int query_len,
 							  int encoding, bool sticky);
 static void entry_dealloc(void);
@@ -471,13 +474,14 @@ _PG_init(void)
 
 	MarkGUCPrefixReserved("pg_stat_statements");
 
+	/*
+	 * Register our shared memory needs.
+	 */
+	RegisterShmemCallbacks(&pgss_shmem_callbacks);
+
 	/*
 	 * Install hooks.
 	 */
-	prev_shmem_request_hook = shmem_request_hook;
-	shmem_request_hook = pgss_shmem_request;
-	prev_shmem_startup_hook = shmem_startup_hook;
-	shmem_startup_hook = pgss_shmem_startup;
 	prev_post_parse_analyze_hook = post_parse_analyze_hook;
 	post_parse_analyze_hook = pgss_post_parse_analyze;
 	prev_planner_hook = planner_hook;
@@ -495,30 +499,42 @@ _PG_init(void)
 }
 
 /*
- * shmem_request hook: request additional shared resources.  We'll allocate or
- * attach to the shared resources in pgss_shmem_startup().
+ * shmem request callback: Request shared memory resources.
+ *
+ * This is called at postmaster startup.  Note that the shared memory isn't
+ * allocated here yet, this merely register our needs.
+ *
+ * In EXEC_BACKEND mode, this is also called in each backend, to re-attach to
+ * the shared memory area that was already initialized.
  */
 static void
-pgss_shmem_request(void)
+pgss_shmem_request(void *arg)
 {
-	if (prev_shmem_request_hook)
-		prev_shmem_request_hook();
-
-	RequestAddinShmemSpace(pgss_memsize());
-	RequestNamedLWLockTranche("pg_stat_statements", 1);
+	ShmemRequestHash(.name = "pg_stat_statements hash",
+					 .nelems = pgss_max,
+					 .hash_info.keysize = sizeof(pgssHashKey),
+					 .hash_info.entrysize = sizeof(pgssEntry),
+					 .hash_flags = HASH_ELEM | HASH_BLOBS,
+					 .ptr = &pgss_hash,
+		);
+	ShmemRequestStruct(.name = "pg_stat_statements",
+					   .size = sizeof(pgssSharedState),
+					   .ptr = (void **) &pgss,
+		);
 }
 
 /*
- * shmem_startup hook: allocate or attach to shared memory,
- * then load any pre-existing statistics from file.
- * Also create and load the query-texts file, which is expected to exist
- * (even if empty) while the module is enabled.
+ * shmem init callback: Initialize our shared memory data structures at
+ * postmaster startup.
+ *
+ * Load any pre-existing statistics from file.  Also create and load the
+ * query-texts file, which is expected to exist (even if empty) while the
+ * module is enabled.
  */
 static void
-pgss_shmem_startup(void)
+pgss_shmem_init(void *arg)
 {
-	bool		found;
-	HASHCTL		info;
+	int			tranche_id;
 	FILE	   *file = NULL;
 	FILE	   *qfile = NULL;
 	uint32		header;
@@ -528,59 +544,38 @@ pgss_shmem_startup(void)
 	int			buffer_size;
 	char	   *buffer = NULL;
 
-	if (prev_shmem_startup_hook)
-		prev_shmem_startup_hook();
-
-	/* reset in case this is a restart within the postmaster */
-	pgss = NULL;
-	pgss_hash = NULL;
-
 	/*
-	 * Create or attach to the shared memory state, including hash table
+	 * We already checked that we're loaded from shared_preload_libraries in
+	 * _PG_init(), so we should not get here after postmaster startup.
 	 */
-	LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
-
-	pgss = ShmemInitStruct("pg_stat_statements",
-						   sizeof(pgssSharedState),
-						   &found);
-
-	if (!found)
-	{
-		/* First time through ... */
-		pgss->lock = &(GetNamedLWLockTranche("pg_stat_statements"))->lock;
-		pgss->cur_median_usage = ASSUMED_MEDIAN_INIT;
-		pgss->mean_query_len = ASSUMED_LENGTH_INIT;
-		SpinLockInit(&pgss->mutex);
-		pgss->extent = 0;
-		pgss->n_writers = 0;
-		pgss->gc_count = 0;
-		pgss->stats.dealloc = 0;
-		pgss->stats.stats_reset = GetCurrentTimestamp();
-	}
-
-	info.keysize = sizeof(pgssHashKey);
-	info.entrysize = sizeof(pgssEntry);
-	pgss_hash = ShmemInitHash("pg_stat_statements hash",
-							  pgss_max,
-							  &info,
-							  HASH_ELEM | HASH_BLOBS);
-
-	LWLockRelease(AddinShmemInitLock);
+	Assert(!IsUnderPostmaster);
 
 	/*
-	 * If we're in the postmaster (or a standalone backend...), set up a shmem
-	 * exit hook to dump the statistics to disk.
+	 * Initialize the shmem area with no statistics.
 	 */
-	if (!IsUnderPostmaster)
-		on_shmem_exit(pgss_shmem_shutdown, (Datum) 0);
+	tranche_id = LWLockNewTrancheId("pg_stat_statements");
+	LWLockInitialize(&pgss->lock.lock, tranche_id);
+	pgss->cur_median_usage = ASSUMED_MEDIAN_INIT;
+	pgss->mean_query_len = ASSUMED_LENGTH_INIT;
+	SpinLockInit(&pgss->mutex);
+	pgss->extent = 0;
+	pgss->n_writers = 0;
+	pgss->gc_count = 0;
+	pgss->stats.dealloc = 0;
+	pgss->stats.stats_reset = GetCurrentTimestamp();
+
+	/* The hash table must've also been initialized by now */
+	Assert(pgss_hash != NULL);
 
 	/*
-	 * Done if some other process already completed our initialization.
+	 * Set up a shmem exit hook to dump the statistics to disk on postmaster
+	 * (or standalone backend) exit.
 	 */
-	if (found)
-		return;
+	on_shmem_exit(pgss_shmem_shutdown, (Datum) 0);
 
 	/*
+	 * Load any pre-existing statistics from file.
+	 *
 	 * Note: we don't bother with locks here, because there should be no other
 	 * processes running when this code is reached.
 	 */
@@ -1339,7 +1334,7 @@ pgss_store(const char *query, int64 queryId,
 	key.toplevel = (nesting_level == 0);
 
 	/* Lookup the hash table entry with shared lock. */
-	LWLockAcquire(pgss->lock, LW_SHARED);
+	LWLockAcquire(&pgss->lock.lock, LW_SHARED);
 
 	entry = (pgssEntry *) hash_search(pgss_hash, &key, HASH_FIND, NULL);
 
@@ -1360,11 +1355,11 @@ pgss_store(const char *query, int64 queryId,
 		 */
 		if (jstate)
 		{
-			LWLockRelease(pgss->lock);
+			LWLockRelease(&pgss->lock.lock);
 			norm_query = generate_normalized_query(jstate, query,
 												   query_location,
 												   &query_len);
-			LWLockAcquire(pgss->lock, LW_SHARED);
+			LWLockAcquire(&pgss->lock.lock, LW_SHARED);
 		}
 
 		/* Append new query text to file with only shared lock held */
@@ -1379,8 +1374,8 @@ pgss_store(const char *query, int64 queryId,
 		do_gc = need_gc_qtexts();
 
 		/* Need exclusive lock to make a new hashtable entry - promote */
-		LWLockRelease(pgss->lock);
-		LWLockAcquire(pgss->lock, LW_EXCLUSIVE);
+		LWLockRelease(&pgss->lock.lock);
+		LWLockAcquire(&pgss->lock.lock, LW_EXCLUSIVE);
 
 		/*
 		 * A garbage collection may have occurred while we weren't holding the
@@ -1519,7 +1514,7 @@ pgss_store(const char *query, int64 queryId,
 	}
 
 done:
-	LWLockRelease(pgss->lock);
+	LWLockRelease(&pgss->lock.lock);
 
 	/* We postpone this clean-up until we're out of the lock */
 	if (norm_query)
@@ -1808,7 +1803,7 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo,
 	 * we need to partition the hash table to limit the time spent holding any
 	 * one lock.
 	 */
-	LWLockAcquire(pgss->lock, LW_SHARED);
+	LWLockAcquire(&pgss->lock.lock, LW_SHARED);
 
 	if (showtext)
 	{
@@ -2046,7 +2041,7 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo,
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
 	}
 
-	LWLockRelease(pgss->lock);
+	LWLockRelease(&pgss->lock.lock);
 
 	if (qbuffer)
 		pfree(qbuffer);
@@ -2086,20 +2081,6 @@ pg_stat_statements_info(PG_FUNCTION_ARGS)
 	PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
 }
 
-/*
- * Estimate shared memory space needed.
- */
-static Size
-pgss_memsize(void)
-{
-	Size		size;
-
-	size = MAXALIGN(sizeof(pgssSharedState));
-	size = add_size(size, hash_estimate_size(pgss_max, sizeof(pgssEntry)));
-
-	return size;
-}
-
 /*
  * Allocate a new hashtable entry.
  * caller must hold an exclusive lock on pgss->lock
@@ -2730,7 +2711,7 @@ entry_reset(Oid userid, Oid dbid, int64 queryid, bool minmax_only)
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("pg_stat_statements must be loaded via \"shared_preload_libraries\"")));
 
-	LWLockAcquire(pgss->lock, LW_EXCLUSIVE);
+	LWLockAcquire(&pgss->lock.lock, LW_EXCLUSIVE);
 	num_entries = hash_get_num_entries(pgss_hash);
 
 	stats_reset = GetCurrentTimestamp();
@@ -2824,7 +2805,7 @@ done:
 	record_gc_qtexts();
 
 release_lock:
-	LWLockRelease(pgss->lock);
+	LWLockRelease(&pgss->lock.lock);
 
 	return stats_reset;
 }
-- 
2.34.1



  [text/x-patch] v20260405-0008-refactor-predicate.c-inline-SerialInit-to-.patch (3.6K, 7-v20260405-0008-refactor-predicate.c-inline-SerialInit-to-.patch)
  download | inline diff:
From ebbbd773993ed6c04d1be6fa7750abac3b118f6d Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Thu, 19 Mar 2026 17:21:30 +0200
Subject: [PATCH v20260405 08/15] refactor predicate.c: inline SerialInit to
 the caller

The ShmemInit function is very complicated currently. These
refactorings move it in a direction that is more natural with the new
shmem callbacks.
---
 src/backend/storage/lmgr/predicate.c | 73 +++++++++++-----------------
 1 file changed, 29 insertions(+), 44 deletions(-)

diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c
index e003fa5b107..13a6a4b93a6 100644
--- a/src/backend/storage/lmgr/predicate.c
+++ b/src/backend/storage/lmgr/predicate.c
@@ -444,7 +444,6 @@ static void FlagSxactUnsafe(SERIALIZABLEXACT *sxact);
 
 static bool SerialPagePrecedesLogically(int64 page1, int64 page2);
 static int	serial_errdetail_for_io_error(const void *opaque_data);
-static void SerialInit(void);
 static void SerialAdd(TransactionId xid, SerCommitSeqNo minConflictCommitSeqNo);
 static SerCommitSeqNo SerialGetMinConflictCommitSeqNo(TransactionId xid);
 static void SerialSetActiveSerXmin(TransactionId xid);
@@ -809,48 +808,6 @@ SerialPagePrecedesLogicallyUnitTests(void)
 }
 #endif
 
-/*
- * Initialize for the tracking of old serializable committed xids.
- */
-static void
-SerialInit(void)
-{
-	bool		found;
-
-	/*
-	 * Set up SLRU management of the pg_serial data.
-	 */
-	SerialSlruCtl->PagePrecedes = SerialPagePrecedesLogically;
-	SerialSlruCtl->errdetail_for_io_error = serial_errdetail_for_io_error;
-	SimpleLruInit(SerialSlruCtl, "serializable",
-				  serializable_buffers, 0, "pg_serial",
-				  LWTRANCHE_SERIAL_BUFFER, LWTRANCHE_SERIAL_SLRU,
-				  SYNC_HANDLER_NONE, false);
-#ifdef USE_ASSERT_CHECKING
-	SerialPagePrecedesLogicallyUnitTests();
-#endif
-	SlruPagePrecedesUnitTests(SerialSlruCtl, SERIAL_ENTRIESPERPAGE);
-
-	/*
-	 * Create or attach to the SerialControl structure.
-	 */
-	serialControl = (SerialControl)
-		ShmemInitStruct("SerialControlData", sizeof(SerialControlData), &found);
-
-	Assert(found == IsUnderPostmaster);
-	if (!found)
-	{
-		/*
-		 * Set control information to reflect empty SLRU.
-		 */
-		LWLockAcquire(SerialControlLock, LW_EXCLUSIVE);
-		serialControl->headPage = -1;
-		serialControl->headXid = InvalidTransactionId;
-		serialControl->tailXid = InvalidTransactionId;
-		LWLockRelease(SerialControlLock);
-	}
-}
-
 /*
  * GUC check_hook for serializable_buffers
  */
@@ -1355,7 +1312,35 @@ PredicateLockShmemInit(void)
 	 * Initialize the SLRU storage for old committed serializable
 	 * transactions.
 	 */
-	SerialInit();
+	SerialSlruCtl->PagePrecedes = SerialPagePrecedesLogically;
+	SerialSlruCtl->errdetail_for_io_error = serial_errdetail_for_io_error;
+	SimpleLruInit(SerialSlruCtl, "serializable",
+				  serializable_buffers, 0, "pg_serial",
+				  LWTRANCHE_SERIAL_BUFFER, LWTRANCHE_SERIAL_SLRU,
+				  SYNC_HANDLER_NONE, false);
+#ifdef USE_ASSERT_CHECKING
+	SerialPagePrecedesLogicallyUnitTests();
+#endif
+	SlruPagePrecedesUnitTests(SerialSlruCtl, SERIAL_ENTRIESPERPAGE);
+
+	/*
+	 * Create or attach to the SerialControl structure.
+	 */
+	serialControl = (SerialControl)
+		ShmemInitStruct("SerialControlData", sizeof(SerialControlData), &found);
+
+	Assert(found == IsUnderPostmaster);
+	if (!found)
+	{
+		/*
+		 * Set control information to reflect empty SLRU.
+		 */
+		LWLockAcquire(SerialControlLock, LW_EXCLUSIVE);
+		serialControl->headPage = -1;
+		serialControl->headXid = InvalidTransactionId;
+		serialControl->tailXid = InvalidTransactionId;
+		LWLockRelease(SerialControlLock);
+	}
 }
 
 /*
-- 
2.34.1



  [text/x-patch] v20260405-0006-Convert-lwlock.c-to-use-the-new-interface.patch (6.4K, 8-v20260405-0006-Convert-lwlock.c-to-use-the-new-interface.patch)
  download | inline diff:
From ea55eca0d105597b08d9a77f1055bbb088972515 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Thu, 2 Apr 2026 00:18:05 +0300
Subject: [PATCH v20260405 06/15] Convert lwlock.c to use the new interface

It seems like a good candidate to convert first because it needs to
initialized before any other subsystem, but other than that it's
nothing special.
---
 src/backend/storage/ipc/ipci.c      | 13 ------
 src/backend/storage/lmgr/lwlock.c   | 71 +++++++++++++++--------------
 src/include/storage/lwlock.h        |  2 -
 src/include/storage/subsystemlist.h |  9 +++-
 4 files changed, 45 insertions(+), 50 deletions(-)

diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index e4a6a52f12d..de65a9ef33c 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -121,7 +121,6 @@ CalculateShmemSize(void)
 	size = add_size(size, TwoPhaseShmemSize());
 	size = add_size(size, BackgroundWorkerShmemSize());
 	size = add_size(size, MultiXactShmemSize());
-	size = add_size(size, LWLockShmemSize());
 	size = add_size(size, ProcArrayShmemSize());
 	size = add_size(size, BackendStatusShmemSize());
 	size = add_size(size, SharedInvalShmemSize());
@@ -179,11 +178,6 @@ AttachSharedMemoryStructs(void)
 	 */
 	InitializeFastPathLocks();
 
-	/*
-	 * Attach to LWLocks first. They are needed by most other subsystems.
-	 */
-	LWLockShmemInit();
-
 	/* Establish pointers to all shared memory areas in this backend */
 	ShmemAttachRequested();
 	CreateOrAttachShmemStructs();
@@ -230,13 +224,6 @@ CreateSharedMemoryAndSemaphores(void)
 	 */
 	InitShmemAllocator(seghdr);
 
-	/*
-	 * Initialize LWLocks first, in case any of the shmem init function use
-	 * LWLocks.  (Nothing else can be running during startup, so they don't
-	 * need to do any locking yet, but we nevertheless allow it.)
-	 */
-	LWLockShmemInit();
-
 	/* Initialize all shmem areas */
 	ShmemInitRequested();
 
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 5cb696490d6..30b715ab051 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -84,6 +84,7 @@
 #include "storage/proclist.h"
 #include "storage/procnumber.h"
 #include "storage/spin.h"
+#include "storage/subsystems.h"
 #include "utils/memutils.h"
 #include "utils/wait_event.h"
 
@@ -212,6 +213,15 @@ typedef struct NamedLWLockTrancheRequest
 
 static List *NamedLWLockTrancheRequests = NIL;
 
+static void LWLockShmemRequest(void *arg);
+static void LWLockShmemInit(void *arg);
+
+const ShmemCallbacks LWLockCallbacks = {
+	.request_fn = LWLockShmemRequest,
+	.init_fn = LWLockShmemInit,
+};
+
+
 static void InitializeLWLocks(int numLocks);
 static inline void LWLockReportWaitStart(LWLock *lock);
 static inline void LWLockReportWaitEnd(void);
@@ -401,58 +411,51 @@ NumLWLocksForNamedTranches(void)
 }
 
 /*
- * Compute shmem space needed for user-defined tranches and the main LWLock
- * array.
+ * Request shmem space for user-defined tranches and the main LWLock array.
  */
-Size
-LWLockShmemSize(void)
+static void
+LWLockShmemRequest(void *arg)
 {
-	Size		size;
 	int			numLocks;
+	Size		size;
+
+	numLocks = NUM_FIXED_LWLOCKS + NumLWLocksForNamedTranches();
 
 	/* Space for user-defined tranches */
 	size = sizeof(LWLockTrancheShmemData);
-
-	/* Space for the LWLock array */
-	numLocks = NUM_FIXED_LWLOCKS + NumLWLocksForNamedTranches();
 	size = add_size(size, mul_size(numLocks, sizeof(LWLockPadded)));
+	ShmemRequestStruct(.name = "LWLock tranches",
+					   .size = size,
+					   .ptr = (void **) &LWLockTranches,
+		);
 
-	return size;
+	/* Space for the LWLock array */
+	ShmemRequestStruct(.name = "Main LWLock array",
+					   .size = numLocks * sizeof(LWLockPadded),
+					   .ptr = (void **) &MainLWLockArray,
+		);
 }
 
 /*
- * Allocate shmem space for user-defined tranches and the main LWLock array,
- * and initialize it.
+ * Initialize shmem space for user-defined tranches and the main LWLock array.
  */
-void
-LWLockShmemInit(void)
+static void
+LWLockShmemInit(void *arg)
 {
 	int			numLocks;
-	bool		found;
 
-	LWLockTranches = (LWLockTrancheShmemData *)
-		ShmemInitStruct("LWLock tranches", sizeof(LWLockTrancheShmemData), &found);
-	if (!found)
-	{
-		/* Calculate total number of locks needed in the main array */
-		LWLockTranches->num_main_array_locks =
-			NUM_FIXED_LWLOCKS + NumLWLocksForNamedTranches();
+	numLocks = NUM_FIXED_LWLOCKS + NumLWLocksForNamedTranches();
 
-		/* Initialize the dynamic-allocation counter for tranches */
-		LWLockTranches->num_user_defined = 0;
+	/* Remember total number of locks needed in the main array */
+	LWLockTranches->num_main_array_locks = numLocks;
 
-		SpinLockInit(&LWLockTranches->lock);
-	}
+	/* Initialize the dynamic-allocation counter for tranches */
+	LWLockTranches->num_user_defined = 0;
 
-	/* Allocate and initialize the main array */
-	numLocks = LWLockTranches->num_main_array_locks;
-	MainLWLockArray = (LWLockPadded *)
-		ShmemInitStruct("Main LWLock array", numLocks * sizeof(LWLockPadded), &found);
-	if (!found)
-	{
-		/* Initialize all LWLocks */
-		InitializeLWLocks(numLocks);
-	}
+	SpinLockInit(&LWLockTranches->lock);
+
+	/* Allocate and initialize all LWLocks in the main array */
+	InitializeLWLocks(numLocks);
 }
 
 /*
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 61f0dbe749a..efa5b427e9f 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -126,8 +126,6 @@ extern bool LWLockHeldByMeInMode(LWLock *lock, LWLockMode mode);
 extern bool LWLockWaitForVar(LWLock *lock, pg_atomic_uint64 *valptr, uint64 oldval, uint64 *newval);
 extern void LWLockUpdateVar(LWLock *lock, pg_atomic_uint64 *valptr, uint64 val);
 
-extern Size LWLockShmemSize(void);
-extern void LWLockShmemInit(void);
 extern void InitLWLockAccess(void);
 
 extern const char *GetLWLockIdentifier(uint32 classId, uint16 eventId);
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index ed43c90bcc3..f0cf01f5a85 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -20,4 +20,11 @@
  * of these matter.
  */
 
-/* TODO: empty for now */
+/*
+ * LWLocks first, in case any of the other shmem init functions use LWLocks.
+ * (Nothing else can be running during startup, so they don't need to do any
+ * locking yet, but we nevertheless allow it.)
+ */
+PG_SHMEM_SUBSYSTEM(LWLockCallbacks)
+
+/* TODO: nothing else for now */
-- 
2.34.1



  [text/x-patch] v20260405-0009-refactor-predicate.c-Move-all-the-initiali.patch (8.3K, 9-v20260405-0009-refactor-predicate.c-Move-all-the-initiali.patch)
  download | inline diff:
From 51ffe05a437c83f0a019372eaa85f9d863996ec3 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Fri, 20 Mar 2026 20:27:50 +0200
Subject: [PATCH v20260405 09/15] refactor predicate.c: Move all the
 initialization together

The ShmemInit function is very complicated currently. These
refactorings move it in a direction that is more natural with the new
shmem callbacks.
---
 src/backend/storage/lmgr/predicate.c | 164 +++++++++++++--------------
 1 file changed, 79 insertions(+), 85 deletions(-)

diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c
index 13a6a4b93a6..af03071a71f 100644
--- a/src/backend/storage/lmgr/predicate.c
+++ b/src/backend/storage/lmgr/predicate.c
@@ -1144,19 +1144,6 @@ PredicateLockShmemInit(void)
 											HASH_ELEM | HASH_BLOBS |
 											HASH_PARTITION | HASH_FIXED_SIZE);
 
-	/*
-	 * Reserve a dummy entry in the hash table; we use it to make sure there's
-	 * always one entry available when we need to split or combine a page,
-	 * because running out of space there could mean aborting a
-	 * non-serializable transaction.
-	 */
-	if (!IsUnderPostmaster)
-	{
-		(void) hash_search(PredicateLockTargetHash, &ScratchTargetTag,
-						   HASH_ENTER, &found);
-		Assert(!found);
-	}
-
 	/* Pre-calculate the hash and partition lock of the scratch entry */
 	ScratchTargetTagHash = PredicateLockTargetTagHashCode(&ScratchTargetTag);
 	ScratchPartitionLock = PredicateLockHashPartitionLock(ScratchTargetTagHash);
@@ -1200,49 +1187,6 @@ PredicateLockShmemInit(void)
 							   requestSize,
 							   &found);
 	Assert(found == IsUnderPostmaster);
-	if (!found)
-	{
-		int			i;
-
-		/* clean everything, both the header and the element */
-		memset(PredXact, 0, requestSize);
-
-		dlist_init(&PredXact->availableList);
-		dlist_init(&PredXact->activeList);
-		PredXact->SxactGlobalXmin = InvalidTransactionId;
-		PredXact->SxactGlobalXminCount = 0;
-		PredXact->WritableSxactCount = 0;
-		PredXact->LastSxactCommitSeqNo = FirstNormalSerCommitSeqNo - 1;
-		PredXact->CanPartialClearThrough = 0;
-		PredXact->HavePartialClearedThrough = 0;
-		PredXact->element
-			= (SERIALIZABLEXACT *) ((char *) PredXact + PredXactListDataSize);
-		/* Add all elements to available list, clean. */
-		for (i = 0; i < max_serializable_xacts; i++)
-		{
-			LWLockInitialize(&PredXact->element[i].perXactPredicateListLock,
-							 LWTRANCHE_PER_XACT_PREDICATE_LIST);
-			dlist_push_tail(&PredXact->availableList, &PredXact->element[i].xactLink);
-		}
-		PredXact->OldCommittedSxact = CreatePredXact();
-		SetInvalidVirtualTransactionId(PredXact->OldCommittedSxact->vxid);
-		PredXact->OldCommittedSxact->prepareSeqNo = 0;
-		PredXact->OldCommittedSxact->commitSeqNo = 0;
-		PredXact->OldCommittedSxact->SeqNo.lastCommitBeforeSnapshot = 0;
-		dlist_init(&PredXact->OldCommittedSxact->outConflicts);
-		dlist_init(&PredXact->OldCommittedSxact->inConflicts);
-		dlist_init(&PredXact->OldCommittedSxact->predicateLocks);
-		dlist_node_init(&PredXact->OldCommittedSxact->finishedLink);
-		dlist_init(&PredXact->OldCommittedSxact->possibleUnsafeConflicts);
-		PredXact->OldCommittedSxact->topXid = InvalidTransactionId;
-		PredXact->OldCommittedSxact->finishedBefore = InvalidTransactionId;
-		PredXact->OldCommittedSxact->xmin = InvalidTransactionId;
-		PredXact->OldCommittedSxact->flags = SXACT_FLAG_COMMITTED;
-		PredXact->OldCommittedSxact->pid = 0;
-		PredXact->OldCommittedSxact->pgprocno = INVALID_PROC_NUMBER;
-	}
-	/* This never changes, so let's keep a local copy. */
-	OldCommittedSxact = PredXact->OldCommittedSxact;
 
 	/*
 	 * Allocate hash table for SERIALIZABLEXID structs.  This stores per-xid
@@ -1278,23 +1222,6 @@ PredicateLockShmemInit(void)
 									 requestSize,
 									 &found);
 	Assert(found == IsUnderPostmaster);
-	if (!found)
-	{
-		int			i;
-
-		/* clean everything, including the elements */
-		memset(RWConflictPool, 0, requestSize);
-
-		dlist_init(&RWConflictPool->availableList);
-		RWConflictPool->element = (RWConflict) ((char *) RWConflictPool +
-												RWConflictPoolHeaderDataSize);
-		/* Add all elements to available list, clean. */
-		for (i = 0; i < max_rw_conflicts; i++)
-		{
-			dlist_push_tail(&RWConflictPool->availableList,
-							&RWConflictPool->element[i].outLink);
-		}
-	}
 
 	/*
 	 * Create or attach to the header for the list of finished serializable
@@ -1305,8 +1232,6 @@ PredicateLockShmemInit(void)
 						sizeof(dlist_head),
 						&found);
 	Assert(found == IsUnderPostmaster);
-	if (!found)
-		dlist_init(FinishedSerializableTransactions);
 
 	/*
 	 * Initialize the SLRU storage for old committed serializable
@@ -1328,19 +1253,88 @@ PredicateLockShmemInit(void)
 	 */
 	serialControl = (SerialControl)
 		ShmemInitStruct("SerialControlData", sizeof(SerialControlData), &found);
-
 	Assert(found == IsUnderPostmaster);
-	if (!found)
+
+	/*
+	 * If we just attached to existing shared memory (EXEC_BACKEND), we're all
+	 * done.  Otherwise, during postmaster startup proceed to initialize the
+	 * shared memory.
+	 */
+	if (IsUnderPostmaster)
 	{
-		/*
-		 * Set control information to reflect empty SLRU.
-		 */
-		LWLockAcquire(SerialControlLock, LW_EXCLUSIVE);
-		serialControl->headPage = -1;
-		serialControl->headXid = InvalidTransactionId;
-		serialControl->tailXid = InvalidTransactionId;
-		LWLockRelease(SerialControlLock);
+		/* This never changes, so let's keep a local copy. */
+		OldCommittedSxact = PredXact->OldCommittedSxact;
+		return;
+	}
+
+	/*
+	 * Reserve a dummy entry in the hash table; we use it to make sure there's
+	 * always one entry available when we need to split or combine a page,
+	 * because running out of space there could mean aborting a
+	 * non-serializable transaction.
+	 */
+	(void) hash_search(PredicateLockTargetHash, &ScratchTargetTag,
+					   HASH_ENTER, &found);
+	Assert(!found);
+
+	/* Initialize PredXact list */
+	dlist_init(&PredXact->availableList);
+	dlist_init(&PredXact->activeList);
+	PredXact->SxactGlobalXmin = InvalidTransactionId;
+	PredXact->SxactGlobalXminCount = 0;
+	PredXact->WritableSxactCount = 0;
+	PredXact->LastSxactCommitSeqNo = FirstNormalSerCommitSeqNo - 1;
+	PredXact->CanPartialClearThrough = 0;
+	PredXact->HavePartialClearedThrough = 0;
+	PredXact->element
+		= (SERIALIZABLEXACT *) ((char *) PredXact + PredXactListDataSize);
+	/* Add all elements to available list, clean. */
+	for (int i = 0; i < max_serializable_xacts; i++)
+	{
+		LWLockInitialize(&PredXact->element[i].perXactPredicateListLock,
+						 LWTRANCHE_PER_XACT_PREDICATE_LIST);
+		dlist_push_tail(&PredXact->availableList, &PredXact->element[i].xactLink);
 	}
+	PredXact->OldCommittedSxact = CreatePredXact();
+	SetInvalidVirtualTransactionId(PredXact->OldCommittedSxact->vxid);
+	PredXact->OldCommittedSxact->prepareSeqNo = 0;
+	PredXact->OldCommittedSxact->commitSeqNo = 0;
+	PredXact->OldCommittedSxact->SeqNo.lastCommitBeforeSnapshot = 0;
+	dlist_init(&PredXact->OldCommittedSxact->outConflicts);
+	dlist_init(&PredXact->OldCommittedSxact->inConflicts);
+	dlist_init(&PredXact->OldCommittedSxact->predicateLocks);
+	dlist_node_init(&PredXact->OldCommittedSxact->finishedLink);
+	dlist_init(&PredXact->OldCommittedSxact->possibleUnsafeConflicts);
+	PredXact->OldCommittedSxact->topXid = InvalidTransactionId;
+	PredXact->OldCommittedSxact->finishedBefore = InvalidTransactionId;
+	PredXact->OldCommittedSxact->xmin = InvalidTransactionId;
+	PredXact->OldCommittedSxact->flags = SXACT_FLAG_COMMITTED;
+	PredXact->OldCommittedSxact->pid = 0;
+	PredXact->OldCommittedSxact->pgprocno = INVALID_PROC_NUMBER;
+
+	/* This never changes, so let's keep a local copy. */
+	OldCommittedSxact = PredXact->OldCommittedSxact;
+
+	/* Initialize the rw-conflict pool */
+	dlist_init(&RWConflictPool->availableList);
+	RWConflictPool->element = (RWConflict) ((char *) RWConflictPool +
+											RWConflictPoolHeaderDataSize);
+	/* Add all elements to available list, clean. */
+	for (int i = 0; i < max_rw_conflicts; i++)
+	{
+		dlist_push_tail(&RWConflictPool->availableList,
+						&RWConflictPool->element[i].outLink);
+	}
+
+	/* Initialize the list of finished serializable transactions */
+	dlist_init(FinishedSerializableTransactions);
+
+	/* Initialize SerialControl to reflect empty SLRU. */
+	LWLockAcquire(SerialControlLock, LW_EXCLUSIVE);
+	serialControl->headPage = -1;
+	serialControl->headXid = InvalidTransactionId;
+	serialControl->tailXid = InvalidTransactionId;
+	LWLockRelease(SerialControlLock);
 }
 
 /*
-- 
2.34.1



  [text/x-patch] v20260405-0007-Use-the-new-mechanism-in-a-few-core-subsys.patch (46.4K, 10-v20260405-0007-Use-the-new-mechanism-in-a-few-core-subsys.patch)
  download | inline diff:
From 335d0842ca9f456649f0464a45ebd185f3c8ca4a Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Thu, 2 Apr 2026 00:21:17 +0300
Subject: [PATCH v20260405 07/15] Use the new mechanism in a few core
 subsystems

I chose these subsystems specifically because they have some
complicating properties, making them slightly harder to convert than
most:

- The initialization callbacks of some of these subsystems have
  dependencies, i.e. they need to be initialized in the right order.

- The ProgGlobal pointer still needs to be inherited by the
  BackendParameters mechanism on EXEC_BACKEND builds, because
  ProcGlobal is required by InitProcess() to get a PGPROC entry, and
  the PGPROC entry is required to use LWLocks, and usually attaching
  to shared memory areas requires the use of LWLocks.

- Similarly, ProcSignal pointer still needs to be handled by
  BackendParameters, because query cancellation connections access it
  without calling InitProcess

I'm believe converting all the rest of the subsystems after this will
be pretty mechanic.

Reviewed-by: Ashutosh Bapat <[email protected]>
Reviewed-by: Zsolt Parragi <[email protected]>
Discussion: https://www.postgresql.org/message-id/CAExHW5vM1bneLYfg0wGeAa=52UiJ3z4vKd3AJ72X8Fw6k3KKrg@mail.gmail.com
---
 src/backend/access/transam/twophase.c  |   2 +-
 src/backend/access/transam/varsup.c    |  35 ++---
 src/backend/port/posix_sema.c          |  22 ++-
 src/backend/port/sysv_sema.c           |  21 ++-
 src/backend/port/win32_sema.c          |  11 +-
 src/backend/storage/ipc/dsm.c          |  64 +++++----
 src/backend/storage/ipc/dsm_registry.c |  36 ++---
 src/backend/storage/ipc/ipci.c         |  28 ----
 src/backend/storage/ipc/latch.c        |   8 +-
 src/backend/storage/ipc/pmsignal.c     |  51 ++++---
 src/backend/storage/ipc/procarray.c    | 110 +++++++-------
 src/backend/storage/ipc/procsignal.c   |  64 ++++-----
 src/backend/storage/ipc/sinvaladt.c    |  38 ++---
 src/backend/storage/lmgr/proc.c        | 191 +++++++++++++------------
 src/backend/utils/hash/dynahash.c      |   3 +-
 src/include/access/transam.h           |   2 -
 src/include/storage/dsm.h              |   3 -
 src/include/storage/dsm_registry.h     |   2 -
 src/include/storage/pg_sema.h          |   6 +-
 src/include/storage/pmsignal.h         |   2 -
 src/include/storage/proc.h             |   2 -
 src/include/storage/procarray.h        |   2 -
 src/include/storage/procsignal.h       |   3 -
 src/include/storage/sinvaladt.h        |   2 -
 src/include/storage/subsystemlist.h    |  17 ++-
 25 files changed, 344 insertions(+), 381 deletions(-)

diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c
index d468c9774b3..ab1cbd67bac 100644
--- a/src/backend/access/transam/twophase.c
+++ b/src/backend/access/transam/twophase.c
@@ -282,7 +282,7 @@ TwoPhaseShmemInit(void)
 			gxacts[i].next = TwoPhaseState->freeGXacts;
 			TwoPhaseState->freeGXacts = &gxacts[i];
 
-			/* associate it with a PGPROC assigned by InitProcGlobal */
+			/* associate it with a PGPROC assigned by ProcGlobalShmemInit */
 			gxacts[i].pgprocno = GetNumberFromPGProc(&PreparedXactProcs[i]);
 		}
 	}
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index 1441a051773..dc5e32d86f3 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -23,6 +23,7 @@
 #include "postmaster/autovacuum.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
 
@@ -30,35 +31,25 @@
 /* Number of OIDs to prefetch (preallocate) per XLOG write */
 #define VAR_OID_PREFETCH		8192
 
+static void VarsupShmemRequest(void *arg);
+
 /* pointer to variables struct in shared memory */
 TransamVariablesData *TransamVariables = NULL;
 
+const ShmemCallbacks VarsupShmemCallbacks = {
+	.request_fn = VarsupShmemRequest,
+};
 
 /*
- * Initialization of shared memory for TransamVariables.
+ * Request shared memory for TransamVariables.
  */
-Size
-VarsupShmemSize(void)
-{
-	return sizeof(TransamVariablesData);
-}
-
-void
-VarsupShmemInit(void)
+static void
+VarsupShmemRequest(void *arg)
 {
-	bool		found;
-
-	/* Initialize our shared state struct */
-	TransamVariables = ShmemInitStruct("TransamVariables",
-									   sizeof(TransamVariablesData),
-									   &found);
-	if (!IsUnderPostmaster)
-	{
-		Assert(!found);
-		memset(TransamVariables, 0, sizeof(TransamVariablesData));
-	}
-	else
-		Assert(found);
+	ShmemRequestStruct(.name = "TransamVariables",
+					   .size = sizeof(TransamVariablesData),
+					   .ptr = (void **) &TransamVariables,
+		);
 }
 
 /*
diff --git a/src/backend/port/posix_sema.c b/src/backend/port/posix_sema.c
index 40205b7d400..53e4a7a5c38 100644
--- a/src/backend/port/posix_sema.c
+++ b/src/backend/port/posix_sema.c
@@ -159,22 +159,24 @@ PosixSemaphoreKill(sem_t *sem)
 
 
 /*
- * Report amount of shared memory needed for semaphores
+ * Request shared memory needed for semaphores
  */
-Size
-PGSemaphoreShmemSize(int maxSemas)
+void
+PGSemaphoreShmemRequest(int maxSemas)
 {
 #ifdef USE_NAMED_POSIX_SEMAPHORES
 	/* No shared memory needed in this case */
-	return 0;
 #else
 	/* Need a PGSemaphoreData per semaphore */
-	return mul_size(maxSemas, sizeof(PGSemaphoreData));
+	ShmemRequestStruct(.name = "Semaphores",
+					   .size = mul_size(maxSemas, sizeof(PGSemaphoreData)),
+					   .ptr = (void **) &sharedSemas,
+		);
 #endif
 }
 
 /*
- * PGReserveSemaphores --- initialize semaphore support
+ * PGSemaphoreInit --- initialize semaphore support
  *
  * This is called during postmaster start or shared memory reinitialization.
  * It should do whatever is needed to be able to support up to maxSemas
@@ -193,10 +195,9 @@ PGSemaphoreShmemSize(int maxSemas)
  * we don't have to expose the counters to other processes.)
  */
 void
-PGReserveSemaphores(int maxSemas)
+PGSemaphoreInit(int maxSemas)
 {
 	struct stat statbuf;
-	bool		found;
 
 	/*
 	 * We use the data directory's inode number to seed the search for free
@@ -214,11 +215,6 @@ PGReserveSemaphores(int maxSemas)
 	mySemPointers = (sem_t **) malloc(maxSemas * sizeof(sem_t *));
 	if (mySemPointers == NULL)
 		elog(PANIC, "out of memory");
-#else
-
-	sharedSemas = (PGSemaphore)
-		ShmemInitStruct("Semaphores", PGSemaphoreShmemSize(maxSemas), &found);
-	Assert(!found);
 #endif
 
 	numSems = 0;
diff --git a/src/backend/port/sysv_sema.c b/src/backend/port/sysv_sema.c
index 4b2bf84072f..98d99515043 100644
--- a/src/backend/port/sysv_sema.c
+++ b/src/backend/port/sysv_sema.c
@@ -301,16 +301,20 @@ IpcSemaphoreCreate(int numSems)
 
 
 /*
- * Report amount of shared memory needed for semaphores
+ * Request shared memory needed for semaphores
  */
-Size
-PGSemaphoreShmemSize(int maxSemas)
+void
+PGSemaphoreShmemRequest(int maxSemas)
 {
-	return mul_size(maxSemas, sizeof(PGSemaphoreData));
+	/* Need a PGSemaphoreData per semaphore */
+	ShmemRequestStruct(.name = "Semaphores",
+					   .size = mul_size(maxSemas, sizeof(PGSemaphoreData)),
+					   .ptr = (void **) &sharedSemas,
+		);
 }
 
 /*
- * PGReserveSemaphores --- initialize semaphore support
+ * PGSemaphoreInit --- initialize semaphore support
  *
  * This is called during postmaster start or shared memory reinitialization.
  * It should do whatever is needed to be able to support up to maxSemas
@@ -327,10 +331,9 @@ PGSemaphoreShmemSize(int maxSemas)
  * have clobbered.)
  */
 void
-PGReserveSemaphores(int maxSemas)
+PGSemaphoreInit(int maxSemas)
 {
 	struct stat statbuf;
-	bool		found;
 
 	/*
 	 * We use the data directory's inode number to seed the search for free
@@ -344,10 +347,6 @@ PGReserveSemaphores(int maxSemas)
 				 errmsg("could not stat data directory \"%s\": %m",
 						DataDir)));
 
-	sharedSemas = (PGSemaphore)
-		ShmemInitStruct("Semaphores", PGSemaphoreShmemSize(maxSemas), &found);
-	Assert(!found);
-
 	numSharedSemas = 0;
 	maxSharedSemas = maxSemas;
 
diff --git a/src/backend/port/win32_sema.c b/src/backend/port/win32_sema.c
index ba97c9b2d64..a3202554769 100644
--- a/src/backend/port/win32_sema.c
+++ b/src/backend/port/win32_sema.c
@@ -25,17 +25,16 @@ static void ReleaseSemaphores(int code, Datum arg);
 
 
 /*
- * Report amount of shared memory needed for semaphores
+ * Request shared memory needed for semaphores
  */
-Size
-PGSemaphoreShmemSize(int maxSemas)
+void
+PGSemaphoreShmemRequest(int maxSemas)
 {
 	/* No shared memory needed on Windows */
-	return 0;
 }
 
 /*
- * PGReserveSemaphores --- initialize semaphore support
+ * PGSemaphoreInit --- initialize semaphore support
  *
  * In the Win32 implementation, we acquire semaphores on-demand; the
  * maxSemas parameter is just used to size the array that keeps track of
@@ -44,7 +43,7 @@ PGSemaphoreShmemSize(int maxSemas)
  * process exits.
  */
 void
-PGReserveSemaphores(int maxSemas)
+PGSemaphoreInit(int maxSemas)
 {
 	mySemSet = (HANDLE *) malloc(maxSemas * sizeof(HANDLE));
 	if (mySemSet == NULL)
diff --git a/src/backend/storage/ipc/dsm.c b/src/backend/storage/ipc/dsm.c
index 6a5b16392f7..8b69df4ff26 100644
--- a/src/backend/storage/ipc/dsm.c
+++ b/src/backend/storage/ipc/dsm.c
@@ -43,6 +43,7 @@
 #include "storage/lwlock.h"
 #include "storage/pg_shmem.h"
 #include "storage/shmem.h"
+#include "storage/subsystems.h"
 #include "utils/freepage.h"
 #include "utils/memutils.h"
 #include "utils/resowner.h"
@@ -109,6 +110,15 @@ static bool dsm_init_done = false;
 
 /* Preallocated DSM space in the main shared memory region. */
 static void *dsm_main_space_begin = NULL;
+static size_t dsm_main_space_size;
+
+static void dsm_main_space_request(void *arg);
+static void dsm_main_space_init(void *arg);
+
+const ShmemCallbacks dsm_shmem_callbacks = {
+	.request_fn = dsm_main_space_request,
+	.init_fn = dsm_main_space_init,
+};
 
 /*
  * List of dynamic shared memory segments used by this backend.
@@ -464,42 +474,40 @@ dsm_set_control_handle(dsm_handle h)
 #endif
 
 /*
- * Reserve some space in the main shared memory segment for DSM segments.
+ * Reserve space in the main shared memory segment for DSM segments.
  */
-size_t
-dsm_estimate_size(void)
+static void
+dsm_main_space_request(void *arg)
 {
-	return 1024 * 1024 * (size_t) min_dynamic_shared_memory;
+	dsm_main_space_size = 1024 * 1024 * (size_t) min_dynamic_shared_memory;
+
+	if (dsm_main_space_size == 0)
+		return;
+
+	ShmemRequestStruct(.name = "Preallocated DSM",
+					   .size = dsm_main_space_size,
+					   .ptr = &dsm_main_space_begin,
+		);
 }
 
-/*
- * Initialize space in the main shared memory segment for DSM segments.
- */
-void
-dsm_shmem_init(void)
+static void
+dsm_main_space_init(void *arg)
 {
-	size_t		size = dsm_estimate_size();
-	bool		found;
+	FreePageManager *fpm = (FreePageManager *) dsm_main_space_begin;
+	size_t		first_page = 0;
+	size_t		pages;
 
-	if (size == 0)
+	if (dsm_main_space_size == 0)
 		return;
 
-	dsm_main_space_begin = ShmemInitStruct("Preallocated DSM", size, &found);
-	if (!found)
-	{
-		FreePageManager *fpm = (FreePageManager *) dsm_main_space_begin;
-		size_t		first_page = 0;
-		size_t		pages;
-
-		/* Reserve space for the FreePageManager. */
-		while (first_page * FPM_PAGE_SIZE < sizeof(FreePageManager))
-			++first_page;
-
-		/* Initialize it and give it all the rest of the space. */
-		FreePageManagerInitialize(fpm, dsm_main_space_begin);
-		pages = (size / FPM_PAGE_SIZE) - first_page;
-		FreePageManagerPut(fpm, first_page, pages);
-	}
+	/* Reserve space for the FreePageManager. */
+	while (first_page * FPM_PAGE_SIZE < sizeof(FreePageManager))
+		++first_page;
+
+	/* Initialize it and give it all the rest of the space. */
+	FreePageManagerInitialize(fpm, dsm_main_space_begin);
+	pages = (dsm_main_space_size / FPM_PAGE_SIZE) - first_page;
+	FreePageManagerPut(fpm, first_page, pages);
 }
 
 /*
diff --git a/src/backend/storage/ipc/dsm_registry.c b/src/backend/storage/ipc/dsm_registry.c
index 9bfcd616827..2b56977659b 100644
--- a/src/backend/storage/ipc/dsm_registry.c
+++ b/src/backend/storage/ipc/dsm_registry.c
@@ -45,6 +45,7 @@
 #include "storage/dsm_registry.h"
 #include "storage/lwlock.h"
 #include "storage/shmem.h"
+#include "storage/subsystems.h"
 #include "utils/builtins.h"
 #include "utils/memutils.h"
 #include "utils/tuplestore.h"
@@ -57,6 +58,14 @@ typedef struct DSMRegistryCtxStruct
 
 static DSMRegistryCtxStruct *DSMRegistryCtx;
 
+static void DSMRegistryShmemRequest(void *arg);
+static void DSMRegistryShmemInit(void *arg);
+
+const ShmemCallbacks DSMRegistryShmemCallbacks = {
+	.request_fn = DSMRegistryShmemRequest,
+	.init_fn = DSMRegistryShmemInit,
+};
+
 typedef struct NamedDSMState
 {
 	dsm_handle	handle;
@@ -114,27 +123,20 @@ static const dshash_parameters dsh_params = {
 static dsa_area *dsm_registry_dsa;
 static dshash_table *dsm_registry_table;
 
-Size
-DSMRegistryShmemSize(void)
+static void
+DSMRegistryShmemRequest(void *arg)
 {
-	return MAXALIGN(sizeof(DSMRegistryCtxStruct));
+	ShmemRequestStruct(.name = "DSM Registry Data",
+					   .size = sizeof(DSMRegistryCtxStruct),
+					   .ptr = (void **) &DSMRegistryCtx,
+		);
 }
 
-void
-DSMRegistryShmemInit(void)
+static void
+DSMRegistryShmemInit(void *arg)
 {
-	bool		found;
-
-	DSMRegistryCtx = (DSMRegistryCtxStruct *)
-		ShmemInitStruct("DSM Registry Data",
-						DSMRegistryShmemSize(),
-						&found);
-
-	if (!found)
-	{
-		DSMRegistryCtx->dsah = DSA_HANDLE_INVALID;
-		DSMRegistryCtx->dshh = DSHASH_HANDLE_INVALID;
-	}
+	DSMRegistryCtx->dsah = DSA_HANDLE_INVALID;
+	DSMRegistryCtx->dshh = DSHASH_HANDLE_INVALID;
 }
 
 /*
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index de65a9ef33c..4f707158303 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -20,7 +20,6 @@
 #include "access/nbtree.h"
 #include "access/subtrans.h"
 #include "access/syncscan.h"
-#include "access/transam.h"
 #include "access/twophase.h"
 #include "access/xlogprefetcher.h"
 #include "access/xlogrecovery.h"
@@ -42,16 +41,11 @@
 #include "storage/aio_subsys.h"
 #include "storage/bufmgr.h"
 #include "storage/dsm.h"
-#include "storage/dsm_registry.h"
 #include "storage/ipc.h"
 #include "storage/pg_shmem.h"
-#include "storage/pmsignal.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
-#include "storage/procarray.h"
-#include "storage/procsignal.h"
 #include "storage/shmem_internal.h"
-#include "storage/sinvaladt.h"
 #include "storage/subsystems.h"
 #include "utils/guc.h"
 #include "utils/injection_point.h"
@@ -105,14 +99,10 @@ CalculateShmemSize(void)
 	size = add_size(size, ShmemGetRequestedSize());
 
 	/* legacy subsystems */
-	size = add_size(size, dsm_estimate_size());
-	size = add_size(size, DSMRegistryShmemSize());
 	size = add_size(size, BufferManagerShmemSize());
 	size = add_size(size, LockManagerShmemSize());
 	size = add_size(size, PredicateLockShmemSize());
-	size = add_size(size, ProcGlobalShmemSize());
 	size = add_size(size, XLogPrefetchShmemSize());
-	size = add_size(size, VarsupShmemSize());
 	size = add_size(size, XLOGShmemSize());
 	size = add_size(size, XLogRecoveryShmemSize());
 	size = add_size(size, CLOGShmemSize());
@@ -121,11 +111,7 @@ CalculateShmemSize(void)
 	size = add_size(size, TwoPhaseShmemSize());
 	size = add_size(size, BackgroundWorkerShmemSize());
 	size = add_size(size, MultiXactShmemSize());
-	size = add_size(size, ProcArrayShmemSize());
 	size = add_size(size, BackendStatusShmemSize());
-	size = add_size(size, SharedInvalShmemSize());
-	size = add_size(size, PMSignalShmemSize());
-	size = add_size(size, ProcSignalShmemSize());
 	size = add_size(size, CheckpointerShmemSize());
 	size = add_size(size, AutoVacuumShmemSize());
 	size = add_size(size, ReplicationSlotsShmemSize());
@@ -278,13 +264,9 @@ RegisterBuiltinShmemCallbacks(void)
 static void
 CreateOrAttachShmemStructs(void)
 {
-	dsm_shmem_init();
-	DSMRegistryShmemInit();
-
 	/*
 	 * Set up xlog, clog, and buffers
 	 */
-	VarsupShmemInit();
 	XLOGShmemInit();
 	XLogPrefetchShmemInit();
 	XLogRecoveryShmemInit();
@@ -307,23 +289,13 @@ CreateOrAttachShmemStructs(void)
 	/*
 	 * Set up process table
 	 */
-	if (!IsUnderPostmaster)
-		InitProcGlobal();
-	ProcArrayShmemInit();
 	BackendStatusShmemInit();
 	TwoPhaseShmemInit();
 	BackgroundWorkerShmemInit();
 
-	/*
-	 * Set up shared-inval messaging
-	 */
-	SharedInvalShmemInit();
-
 	/*
 	 * Set up interprocess signaling mechanisms
 	 */
-	PMSignalShmemInit();
-	ProcSignalShmemInit();
 	CheckpointerShmemInit();
 	AutoVacuumShmemInit();
 	ReplicationSlotsShmemInit();
diff --git a/src/backend/storage/ipc/latch.c b/src/backend/storage/ipc/latch.c
index 8537e9fef2d..7d4f4cf32bb 100644
--- a/src/backend/storage/ipc/latch.c
+++ b/src/backend/storage/ipc/latch.c
@@ -80,10 +80,10 @@ InitLatch(Latch *latch)
  * current process.
  *
  * InitSharedLatch needs to be called in postmaster before forking child
- * processes, usually right after allocating the shared memory block
- * containing the latch with ShmemInitStruct. (The Unix implementation
- * doesn't actually require that, but the Windows one does.) Because of
- * this restriction, we have no concurrency issues to worry about here.
+ * processes, usually right after initializing the shared memory block
+ * containing the latch. (The Unix implementation doesn't actually require
+ * that, but the Windows one does.) Because of this restriction, we have no
+ * concurrency issues to worry about here.
  *
  * Note that other handles created in this module are never marked as
  * inheritable.  Thus we do not need to worry about cleaning up child
diff --git a/src/backend/storage/ipc/pmsignal.c b/src/backend/storage/ipc/pmsignal.c
index 4618820b337..bdad5fdd043 100644
--- a/src/backend/storage/ipc/pmsignal.c
+++ b/src/backend/storage/ipc/pmsignal.c
@@ -27,6 +27,7 @@
 #include "storage/ipc.h"
 #include "storage/pmsignal.h"
 #include "storage/shmem.h"
+#include "storage/subsystems.h"
 #include "utils/memutils.h"
 
 
@@ -83,6 +84,14 @@ struct PMSignalData
 /* PMSignalState pointer is valid in both postmaster and child processes */
 NON_EXEC_STATIC volatile PMSignalData *PMSignalState = NULL;
 
+static void PMSignalShmemRequest(void *);
+static void PMSignalShmemInit(void *);
+
+const ShmemCallbacks PMSignalShmemCallbacks = {
+	.request_fn = PMSignalShmemRequest,
+	.init_fn = PMSignalShmemInit,
+};
+
 /*
  * Local copy of PMSignalState->num_child_flags, only valid in the
  * postmaster.  Postmaster keeps a local copy so that it doesn't need to
@@ -123,39 +132,29 @@ postmaster_death_handler(SIGNAL_ARGS)
 static void MarkPostmasterChildInactive(int code, Datum arg);
 
 /*
- * PMSignalShmemSize
- *		Compute space needed for pmsignal.c's shared memory
+ * PMSignalShmemRequest - Register pmsignal.c's shared memory needs
  */
-Size
-PMSignalShmemSize(void)
+static void
+PMSignalShmemRequest(void *arg)
 {
-	Size		size;
+	size_t		size;
 
-	size = offsetof(PMSignalData, PMChildFlags);
-	size = add_size(size, mul_size(MaxLivePostmasterChildren(),
-								   sizeof(sig_atomic_t)));
+	num_child_flags = MaxLivePostmasterChildren();
 
-	return size;
+	size = add_size(offsetof(PMSignalData, PMChildFlags),
+					mul_size(num_child_flags, sizeof(sig_atomic_t)));
+	ShmemRequestStruct(.name = "PMSignalState",
+					   .size = size,
+					   .ptr = (void **) &PMSignalState,
+		);
 }
 
-/*
- * PMSignalShmemInit - initialize during shared-memory creation
- */
-void
-PMSignalShmemInit(void)
+static void
+PMSignalShmemInit(void *arg)
 {
-	bool		found;
-
-	PMSignalState = (PMSignalData *)
-		ShmemInitStruct("PMSignalState", PMSignalShmemSize(), &found);
-
-	if (!found)
-	{
-		/* initialize all flags to zeroes */
-		MemSet(unvolatize(PMSignalData *, PMSignalState), 0, PMSignalShmemSize());
-		num_child_flags = MaxLivePostmasterChildren();
-		PMSignalState->num_child_flags = num_child_flags;
-	}
+	Assert(PMSignalState);
+	Assert(num_child_flags > 0);
+	PMSignalState->num_child_flags = num_child_flags;
 }
 
 /*
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index cc207cb56e3..f540bb6b23f 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -61,6 +61,7 @@
 #include "storage/proc.h"
 #include "storage/procarray.h"
 #include "storage/procsignal.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/injection_point.h"
@@ -103,6 +104,18 @@ typedef struct ProcArrayStruct
 	int			pgprocnos[FLEXIBLE_ARRAY_MEMBER];
 } ProcArrayStruct;
 
+static void ProcArrayShmemRequest(void *arg);
+static void ProcArrayShmemInit(void *arg);
+static void ProcArrayShmemAttach(void *arg);
+
+static ProcArrayStruct *procArray;
+
+const struct ShmemCallbacks ProcArrayShmemCallbacks = {
+	.request_fn = ProcArrayShmemRequest,
+	.init_fn = ProcArrayShmemInit,
+	.attach_fn = ProcArrayShmemAttach,
+};
+
 /*
  * State for the GlobalVisTest* family of functions. Those functions can
  * e.g. be used to decide if a deleted row can be removed without violating
@@ -269,9 +282,6 @@ typedef enum KAXCompressReason
 	KAX_STARTUP_PROCESS_IDLE,	/* startup process is about to sleep */
 } KAXCompressReason;
 
-
-static ProcArrayStruct *procArray;
-
 static PGPROC *allProcs;
 
 /*
@@ -282,8 +292,11 @@ static TransactionId cachedXidIsNotInProgress = InvalidTransactionId;
 /*
  * Bookkeeping for tracking emulated transactions in recovery
  */
+
 static TransactionId *KnownAssignedXids;
+
 static bool *KnownAssignedXidsValid;
+
 static TransactionId latestObservedXid = InvalidTransactionId;
 
 /*
@@ -374,19 +387,13 @@ static inline FullTransactionId FullXidRelativeTo(FullTransactionId rel,
 static void GlobalVisUpdateApply(ComputeXidHorizonsResult *horizons);
 
 /*
- * Report shared-memory space needed by ProcArrayShmemInit
+ * Register the shared PGPROC array during postmaster startup.
  */
-Size
-ProcArrayShmemSize(void)
+static void
+ProcArrayShmemRequest(void *arg)
 {
-	Size		size;
-
-	/* Size of the ProcArray structure itself */
 #define PROCARRAY_MAXPROCS	(MaxBackends + max_prepared_xacts)
 
-	size = offsetof(ProcArrayStruct, pgprocnos);
-	size = add_size(size, mul_size(sizeof(int), PROCARRAY_MAXPROCS));
-
 	/*
 	 * During Hot Standby processing we have a data structure called
 	 * KnownAssignedXids, created in shared memory. Local data structures are
@@ -405,64 +412,49 @@ ProcArrayShmemSize(void)
 
 	if (EnableHotStandby)
 	{
-		size = add_size(size,
-						mul_size(sizeof(TransactionId),
-								 TOTAL_MAX_CACHED_SUBXIDS));
-		size = add_size(size,
-						mul_size(sizeof(bool), TOTAL_MAX_CACHED_SUBXIDS));
+		ShmemRequestStruct(.name = "KnownAssignedXids",
+						   .size = mul_size(sizeof(TransactionId), TOTAL_MAX_CACHED_SUBXIDS),
+						   .ptr = (void **) &KnownAssignedXids,
+			);
+
+		ShmemRequestStruct(.name = "KnownAssignedXidsValid",
+						   .size = mul_size(sizeof(bool), TOTAL_MAX_CACHED_SUBXIDS),
+						   .ptr = (void **) &KnownAssignedXidsValid,
+			);
 	}
 
-	return size;
+	/* Register the ProcArray shared structure */
+	ShmemRequestStruct(.name = "Proc Array",
+					   .size = add_size(offsetof(ProcArrayStruct, pgprocnos),
+										mul_size(sizeof(int), PROCARRAY_MAXPROCS)),
+					   .ptr = (void **) &procArray,
+		);
 }
 
 /*
  * Initialize the shared PGPROC array during postmaster startup.
  */
-void
-ProcArrayShmemInit(void)
+static void
+ProcArrayShmemInit(void *arg)
 {
-	bool		found;
-
-	/* Create or attach to the ProcArray shared structure */
-	procArray = (ProcArrayStruct *)
-		ShmemInitStruct("Proc Array",
-						add_size(offsetof(ProcArrayStruct, pgprocnos),
-								 mul_size(sizeof(int),
-										  PROCARRAY_MAXPROCS)),
-						&found);
-
-	if (!found)
-	{
-		/*
-		 * We're the first - initialize.
-		 */
-		procArray->numProcs = 0;
-		procArray->maxProcs = PROCARRAY_MAXPROCS;
-		procArray->maxKnownAssignedXids = TOTAL_MAX_CACHED_SUBXIDS;
-		procArray->numKnownAssignedXids = 0;
-		procArray->tailKnownAssignedXids = 0;
-		procArray->headKnownAssignedXids = 0;
-		procArray->lastOverflowedXid = InvalidTransactionId;
-		procArray->replication_slot_xmin = InvalidTransactionId;
-		procArray->replication_slot_catalog_xmin = InvalidTransactionId;
-		TransamVariables->xactCompletionCount = 1;
-	}
+	procArray->numProcs = 0;
+	procArray->maxProcs = PROCARRAY_MAXPROCS;
+	procArray->maxKnownAssignedXids = TOTAL_MAX_CACHED_SUBXIDS;
+	procArray->numKnownAssignedXids = 0;
+	procArray->tailKnownAssignedXids = 0;
+	procArray->headKnownAssignedXids = 0;
+	procArray->lastOverflowedXid = InvalidTransactionId;
+	procArray->replication_slot_xmin = InvalidTransactionId;
+	procArray->replication_slot_catalog_xmin = InvalidTransactionId;
+	TransamVariables->xactCompletionCount = 1;
 
 	allProcs = ProcGlobal->allProcs;
+}
 
-	/* Create or attach to the KnownAssignedXids arrays too, if needed */
-	if (EnableHotStandby)
-	{
-		KnownAssignedXids = (TransactionId *)
-			ShmemInitStruct("KnownAssignedXids",
-							mul_size(sizeof(TransactionId),
-									 TOTAL_MAX_CACHED_SUBXIDS),
-							&found);
-		KnownAssignedXidsValid = (bool *)
-			ShmemInitStruct("KnownAssignedXidsValid",
-							mul_size(sizeof(bool), TOTAL_MAX_CACHED_SUBXIDS),
-							&found);
-	}
+static void
+ProcArrayShmemAttach(void *arg)
+{
+	allProcs = ProcGlobal->allProcs;
 }
 
 /*
diff --git a/src/backend/storage/ipc/procsignal.c b/src/backend/storage/ipc/procsignal.c
index f1ab3aa3fe0..adebf0e7898 100644
--- a/src/backend/storage/ipc/procsignal.c
+++ b/src/backend/storage/ipc/procsignal.c
@@ -33,6 +33,7 @@
 #include "storage/shmem.h"
 #include "storage/sinval.h"
 #include "storage/smgr.h"
+#include "storage/subsystems.h"
 #include "tcop/tcopprot.h"
 #include "utils/memutils.h"
 #include "utils/wait_event.h"
@@ -106,7 +107,16 @@ struct ProcSignalHeader
 #define BARRIER_CLEAR_BIT(flags, type) \
 	((flags) &= ~(((uint32) 1) << (uint32) (type)))
 
+static void ProcSignalShmemRequest(void *arg);
+static void ProcSignalShmemInit(void *arg);
+
+const ShmemCallbacks ProcSignalShmemCallbacks = {
+	.request_fn = ProcSignalShmemRequest,
+	.init_fn = ProcSignalShmemInit,
+};
+
 NON_EXEC_STATIC ProcSignalHeader *ProcSignal = NULL;
+
 static ProcSignalSlot *MyProcSignalSlot = NULL;
 
 static bool CheckProcSignal(ProcSignalReason reason);
@@ -114,51 +124,39 @@ static void CleanupProcSignalState(int status, Datum arg);
 static void ResetProcSignalBarrierBits(uint32 flags);
 
 /*
- * ProcSignalShmemSize
- *		Compute space needed for ProcSignal's shared memory
+ * ProcSignalShmemRequest
+ *		Register ProcSignal's shared memory needs at postmaster startup
  */
-Size
-ProcSignalShmemSize(void)
+static void
+ProcSignalShmemRequest(void *arg)
 {
 	Size		size;
 
 	size = mul_size(NumProcSignalSlots, sizeof(ProcSignalSlot));
 	size = add_size(size, offsetof(ProcSignalHeader, psh_slot));
-	return size;
+
+	ShmemRequestStruct(.name = "ProcSignal",
+					   .size = size,
+					   .ptr = (void **) &ProcSignal,
+		);
 }
 
-/*
- * ProcSignalShmemInit
- *		Allocate and initialize ProcSignal's shared memory
- */
-void
-ProcSignalShmemInit(void)
+static void
+ProcSignalShmemInit(void *arg)
 {
-	Size		size = ProcSignalShmemSize();
-	bool		found;
+	pg_atomic_init_u64(&ProcSignal->psh_barrierGeneration, 0);
 
-	ProcSignal = (ProcSignalHeader *)
-		ShmemInitStruct("ProcSignal", size, &found);
-
-	/* If we're first, initialize. */
-	if (!found)
+	for (int i = 0; i < NumProcSignalSlots; ++i)
 	{
-		int			i;
-
-		pg_atomic_init_u64(&ProcSignal->psh_barrierGeneration, 0);
+		ProcSignalSlot *slot = &ProcSignal->psh_slot[i];
 
-		for (i = 0; i < NumProcSignalSlots; ++i)
-		{
-			ProcSignalSlot *slot = &ProcSignal->psh_slot[i];
-
-			SpinLockInit(&slot->pss_mutex);
-			pg_atomic_init_u32(&slot->pss_pid, 0);
-			slot->pss_cancel_key_len = 0;
-			MemSet(slot->pss_signalFlags, 0, sizeof(slot->pss_signalFlags));
-			pg_atomic_init_u64(&slot->pss_barrierGeneration, PG_UINT64_MAX);
-			pg_atomic_init_u32(&slot->pss_barrierCheckMask, 0);
-			ConditionVariableInit(&slot->pss_barrierCV);
-		}
+		SpinLockInit(&slot->pss_mutex);
+		pg_atomic_init_u32(&slot->pss_pid, 0);
+		slot->pss_cancel_key_len = 0;
+		MemSet(slot->pss_signalFlags, 0, sizeof(slot->pss_signalFlags));
+		pg_atomic_init_u64(&slot->pss_barrierGeneration, PG_UINT64_MAX);
+		pg_atomic_init_u32(&slot->pss_barrierCheckMask, 0);
+		ConditionVariableInit(&slot->pss_barrierCV);
 	}
 }
 
diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c
index a7a7cc4f0a9..37a21ffaf1a 100644
--- a/src/backend/storage/ipc/sinvaladt.c
+++ b/src/backend/storage/ipc/sinvaladt.c
@@ -25,6 +25,7 @@
 #include "storage/shmem.h"
 #include "storage/sinvaladt.h"
 #include "storage/spin.h"
+#include "storage/subsystems.h"
 
 /*
  * Conceptually, the shared cache invalidation messages are stored in an
@@ -205,6 +206,14 @@ typedef struct SISeg
 
 static SISeg *shmInvalBuffer;	/* pointer to the shared inval buffer */
 
+static void SharedInvalShmemRequest(void *arg);
+static void SharedInvalShmemInit(void *arg);
+
+const ShmemCallbacks SharedInvalShmemCallbacks = {
+	.request_fn = SharedInvalShmemRequest,
+	.init_fn = SharedInvalShmemInit,
+};
+
 
 static LocalTransactionId nextLocalTransactionId;
 
@@ -212,10 +221,11 @@ static void CleanupInvalidationState(int status, Datum arg);
 
 
 /*
- * SharedInvalShmemSize --- return shared-memory space needed
+ * SharedInvalShmemRequest
+ *		Register shared memory needs for the SI message buffer
  */
-Size
-SharedInvalShmemSize(void)
+static void
+SharedInvalShmemRequest(void *arg)
 {
 	Size		size;
 
@@ -223,26 +233,18 @@ SharedInvalShmemSize(void)
 	size = add_size(size, mul_size(sizeof(ProcState), NumProcStateSlots));	/* procState */
 	size = add_size(size, mul_size(sizeof(int), NumProcStateSlots));	/* pgprocnos */
 
-	return size;
+	ShmemRequestStruct(.name = "shmInvalBuffer",
+					   .size = size,
+					   .ptr = (void **) &shmInvalBuffer,
+		);
 }
 
-/*
- * SharedInvalShmemInit
- *		Create and initialize the SI message buffer
- */
-void
-SharedInvalShmemInit(void)
+static void
+SharedInvalShmemInit(void *arg)
 {
 	int			i;
-	bool		found;
-
-	/* Allocate space in shared memory */
-	shmInvalBuffer = (SISeg *)
-		ShmemInitStruct("shmInvalBuffer", SharedInvalShmemSize(), &found);
-	if (found)
-		return;
 
-	/* Clear message counters, save size of procState array, init spinlock */
+	/* Clear message counters, init spinlock */
 	shmInvalBuffer->minMsgNum = 0;
 	shmInvalBuffer->maxMsgNum = 0;
 	shmInvalBuffer->nextThreshold = CLEANUP_MIN;
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 9b880a6af65..a05c55b534e 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -52,6 +52,7 @@
 #include "storage/procsignal.h"
 #include "storage/spin.h"
 #include "storage/standby.h"
+#include "storage/subsystems.h"
 #include "utils/timeout.h"
 #include "utils/timestamp.h"
 #include "utils/wait_event.h"
@@ -70,9 +71,23 @@ PGPROC	   *MyProc = NULL;
 
 /* Pointers to shared-memory structures */
 PROC_HDR   *ProcGlobal = NULL;
+static void *AllProcsShmemPtr;
+static void *FastPathLockArrayShmemPtr;
 NON_EXEC_STATIC PGPROC *AuxiliaryProcs = NULL;
 PGPROC	   *PreparedXactProcs = NULL;
 
+static void ProcGlobalShmemRequest(void *arg);
+static void ProcGlobalShmemInit(void *arg);
+
+const ShmemCallbacks ProcGlobalShmemCallbacks = {
+	.request_fn = ProcGlobalShmemRequest,
+	.init_fn = ProcGlobalShmemInit,
+};
+
+static uint32 TotalProcs;
+static size_t ProcGlobalAllProcsShmemSize;
+static size_t FastPathLockArrayShmemSize;
+
 /* Is a deadlock check pending? */
 static volatile sig_atomic_t got_deadlock_timeout;
 
@@ -83,32 +98,12 @@ static DeadLockState CheckDeadLock(void);
 
 
 /*
- * Report shared-memory space needed by PGPROC.
+ * Calculate shared-memory space needed by Fast-Path locks.
  */
 static Size
-PGProcShmemSize(void)
+CalculateFastPathLockShmemSize(void)
 {
 	Size		size = 0;
-	Size		TotalProcs =
-		add_size(MaxBackends, add_size(NUM_AUXILIARY_PROCS, max_prepared_xacts));
-
-	size = add_size(size, mul_size(TotalProcs, sizeof(PGPROC)));
-	size = add_size(size, mul_size(TotalProcs, sizeof(*ProcGlobal->xids)));
-	size = add_size(size, mul_size(TotalProcs, sizeof(*ProcGlobal->subxidStates)));
-	size = add_size(size, mul_size(TotalProcs, sizeof(*ProcGlobal->statusFlags)));
-
-	return size;
-}
-
-/*
- * Report shared-memory space needed by Fast-Path locks.
- */
-static Size
-FastPathLockShmemSize(void)
-{
-	Size		size = 0;
-	Size		TotalProcs =
-		add_size(MaxBackends, add_size(NUM_AUXILIARY_PROCS, max_prepared_xacts));
 	Size		fpLockBitsSize,
 				fpRelIdSize;
 
@@ -128,26 +123,7 @@ FastPathLockShmemSize(void)
 }
 
 /*
- * Report shared-memory space needed by InitProcGlobal.
- */
-Size
-ProcGlobalShmemSize(void)
-{
-	Size		size = 0;
-
-	/* ProcGlobal */
-	size = add_size(size, sizeof(PROC_HDR));
-	size = add_size(size, sizeof(slock_t));
-
-	size = add_size(size, PGSemaphoreShmemSize(ProcGlobalSemas()));
-	size = add_size(size, PGProcShmemSize());
-	size = add_size(size, FastPathLockShmemSize());
-
-	return size;
-}
-
-/*
- * Report number of semaphores needed by InitProcGlobal.
+ * Report number of semaphores needed by ProcGlobalShmemInit.
  */
 int
 ProcGlobalSemas(void)
@@ -160,7 +136,67 @@ ProcGlobalSemas(void)
 }
 
 /*
- * InitProcGlobal -
+ * ProcGlobalShmemRequest
+ *	  Register shared memory needs.
+ *
+ * This is called during postmaster or standalone backend startup, and also
+ * during backend startup in EXEC_BACKEND mode.
+ */
+static void
+ProcGlobalShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Reserve all the PGPROC structures we'll need.  There are six separate
+	 * consumers: (1) normal backends, (2) autovacuum workers and special
+	 * workers, (3) background workers, (4) walsenders, (5) auxiliary
+	 * processes, and (6) prepared transactions.  (For largely-historical
+	 * reasons, we combine autovacuum and special workers into one category
+	 * with a single freelist.)  Each PGPROC structure is dedicated to exactly
+	 * one of these purposes, and they do not move between groups.
+	 */
+	TotalProcs =
+		add_size(MaxBackends, add_size(NUM_AUXILIARY_PROCS, max_prepared_xacts));
+
+	size = 0;
+	size = add_size(size, mul_size(TotalProcs, sizeof(PGPROC)));
+	size = add_size(size, mul_size(TotalProcs, sizeof(*ProcGlobal->xids)));
+	size = add_size(size, mul_size(TotalProcs, sizeof(*ProcGlobal->subxidStates)));
+	size = add_size(size, mul_size(TotalProcs, sizeof(*ProcGlobal->statusFlags)));
+	ProcGlobalAllProcsShmemSize = size;
+	ShmemRequestStruct(.name = "PGPROC structures",
+					   .size = ProcGlobalAllProcsShmemSize,
+					   .ptr = &AllProcsShmemPtr,
+		);
+
+	if (!IsUnderPostmaster)
+		size = FastPathLockArrayShmemSize = CalculateFastPathLockShmemSize();
+	else
+		size = SHMEM_ATTACH_UNKNOWN_SIZE;
+	ShmemRequestStruct(.name = "Fast-Path Lock Array",
+					   .size = size,
+					   .ptr = &FastPathLockArrayShmemPtr,
+		);
+
+	/*
+	 * ProcGlobal is registered here in .ptr as usual, but it needs to be
+	 * propagated specially in EXEC_BACKEND mode, because ProcGlobal needs to
+	 * be accessed early at backend startup, before ShmemAttachRequested() has
+	 * been called.
+	 */
+	ShmemRequestStruct(.name = "Proc Header",
+					   .size = sizeof(PROC_HDR),
+					   .ptr = (void **) &ProcGlobal,
+		);
+
+	/* Let the semaphore implementation register its shared memory needs */
+	PGSemaphoreShmemRequest(ProcGlobalSemas());
+}
+
+
+/*
+ * ProcGlobalShmemInit -
  *	  Initialize the global process table during postmaster or standalone
  *	  backend startup.
  *
@@ -179,36 +215,23 @@ ProcGlobalSemas(void)
  *	  Another reason for creating semaphores here is that the semaphore
  *	  implementation typically requires us to create semaphores in the
  *	  postmaster, not in backends.
- *
- * Note: this is NOT called by individual backends under a postmaster,
- * not even in the EXEC_BACKEND case.  The ProcGlobal and AuxiliaryProcs
- * pointers must be propagated specially for EXEC_BACKEND operation.
  */
-void
-InitProcGlobal(void)
+static void
+ProcGlobalShmemInit(void *arg)
 {
+	char	   *ptr;
+	size_t		requestSize;
 	PGPROC	   *procs;
 	int			i,
 				j;
-	bool		found;
-	uint32		TotalProcs = MaxBackends + NUM_AUXILIARY_PROCS + max_prepared_xacts;
 
 	/* Used for setup of per-backend fast-path slots. */
 	char	   *fpPtr,
 			   *fpEndPtr PG_USED_FOR_ASSERTS_ONLY;
 	Size		fpLockBitsSize,
 				fpRelIdSize;
-	Size		requestSize;
-	char	   *ptr;
 
-	/* Create the ProcGlobal shared structure */
-	ProcGlobal = (PROC_HDR *)
-		ShmemInitStruct("Proc Header", sizeof(PROC_HDR), &found);
-	Assert(!found);
-
-	/*
-	 * Initialize the data structures.
-	 */
+	Assert(ProcGlobal);
 	ProcGlobal->spins_per_delay = DEFAULT_SPINS_PER_DELAY;
 	SpinLockInit(&ProcGlobal->freeProcsLock);
 	dlist_init(&ProcGlobal->freeProcs);
@@ -221,23 +244,11 @@ InitProcGlobal(void)
 	pg_atomic_init_u32(&ProcGlobal->procArrayGroupFirst, INVALID_PROC_NUMBER);
 	pg_atomic_init_u32(&ProcGlobal->clogGroupFirst, INVALID_PROC_NUMBER);
 
-	/*
-	 * Create and initialize all the PGPROC structures we'll need.  There are
-	 * six separate consumers: (1) normal backends, (2) autovacuum workers and
-	 * special workers, (3) background workers, (4) walsenders, (5) auxiliary
-	 * processes, and (6) prepared transactions.  (For largely-historical
-	 * reasons, we combine autovacuum and special workers into one category
-	 * with a single freelist.)  Each PGPROC structure is dedicated to exactly
-	 * one of these purposes, and they do not move between groups.
-	 */
-	requestSize = PGProcShmemSize();
-
-	ptr = ShmemInitStruct("PGPROC structures",
-						  requestSize,
-						  &found);
-
+	ptr = AllProcsShmemPtr;
+	requestSize = ProcGlobalAllProcsShmemSize;
 	MemSet(ptr, 0, requestSize);
 
+	/* Carve out the allProcs array from the shared memory area */
 	procs = (PGPROC *) ptr;
 	ptr = ptr + TotalProcs * sizeof(PGPROC);
 
@@ -246,7 +257,7 @@ InitProcGlobal(void)
 	ProcGlobal->allProcCount = MaxBackends + NUM_AUXILIARY_PROCS;
 
 	/*
-	 * Allocate arrays mirroring PGPROC fields in a dense manner. See
+	 * Carve out arrays mirroring PGPROC fields in a dense manner. See
 	 * PROC_HDR.
 	 *
 	 * XXX: It might make sense to increase padding for these arrays, given
@@ -261,30 +272,26 @@ InitProcGlobal(void)
 	ProcGlobal->statusFlags = (uint8 *) ptr;
 	ptr = ptr + (TotalProcs * sizeof(*ProcGlobal->statusFlags));
 
-	/* make sure wer didn't overflow */
+	/* make sure we didn't overflow */
 	Assert((ptr > (char *) procs) && (ptr <= (char *) procs + requestSize));
 
 	/*
-	 * Allocate arrays for fast-path locks. Those are variable-length, so
+	 * Initialize arrays for fast-path locks. Those are variable-length, so
 	 * can't be included in PGPROC directly. We allocate a separate piece of
 	 * shared memory and then divide that between backends.
 	 */
 	fpLockBitsSize = MAXALIGN(FastPathLockGroupsPerBackend * sizeof(uint64));
 	fpRelIdSize = MAXALIGN(FastPathLockSlotsPerBackend() * sizeof(Oid));
 
-	requestSize = FastPathLockShmemSize();
-
-	fpPtr = ShmemInitStruct("Fast-Path Lock Array",
-							requestSize,
-							&found);
-
-	MemSet(fpPtr, 0, requestSize);
+	fpPtr = FastPathLockArrayShmemPtr;
+	requestSize = FastPathLockArrayShmemSize;
+	memset(fpPtr, 0, requestSize);
 
 	/* For asserts checking we did not overflow. */
 	fpEndPtr = fpPtr + requestSize;
 
-	/* Reserve space for semaphores. */
-	PGReserveSemaphores(ProcGlobalSemas());
+	/* Initialize semaphores */
+	PGSemaphoreInit(ProcGlobalSemas());
 
 	for (i = 0; i < TotalProcs; i++)
 	{
@@ -405,7 +412,7 @@ InitProcess(void)
 
 	/*
 	 * Decide which list should supply our PGPROC.  This logic must match the
-	 * way the freelists were constructed in InitProcGlobal().
+	 * way the freelists were constructed in ProcGlobalShmemInit().
 	 */
 	if (AmAutoVacuumWorkerProcess() || AmSpecialWorkerProcess())
 		procgloballist = &ProcGlobal->autovacFreeProcs;
@@ -460,7 +467,7 @@ InitProcess(void)
 
 	/*
 	 * Initialize all fields of MyProc, except for those previously
-	 * initialized by InitProcGlobal.
+	 * initialized by ProcGlobalShmemInit.
 	 */
 	dlist_node_init(&MyProc->freeProcsLink);
 	MyProc->waitStatus = PROC_WAIT_STATUS_OK;
@@ -593,7 +600,7 @@ InitProcessPhase2(void)
  * This is called by bgwriter and similar processes so that they will have a
  * MyProc value that's real enough to let them wait for LWLocks.  The PGPROC
  * and sema that are assigned are one of the extra ones created during
- * InitProcGlobal.
+ * ProcGlobalShmemInit.
  *
  * Auxiliary processes are presently not expected to wait for real (lockmgr)
  * locks, so we need not set up the deadlock checker.  They are never added
@@ -662,7 +669,7 @@ InitAuxiliaryProcess(void)
 
 	/*
 	 * Initialize all fields of MyProc, except for those previously
-	 * initialized by InitProcGlobal.
+	 * initialized by ProcGlobalShmemInit.
 	 */
 	dlist_node_init(&MyProc->freeProcsLink);
 	MyProc->waitStatus = PROC_WAIT_STATUS_OK;
diff --git a/src/backend/utils/hash/dynahash.c b/src/backend/utils/hash/dynahash.c
index d49a7a92c64..81199edca86 100644
--- a/src/backend/utils/hash/dynahash.c
+++ b/src/backend/utils/hash/dynahash.c
@@ -338,7 +338,8 @@ string_compare(const char *key1, const char *key2, Size keysize)
  * under info->hcxt rather than under TopMemoryContext; the default
  * behavior is only suitable for session-lifespan hash tables.
  * Other flags bits are special-purpose and seldom used, except for those
- * associated with shared-memory hash tables, for which see ShmemInitHash().
+ * associated with shared-memory hash tables, for which see
+ * ShmemRequestHash().
  *
  * Fields in *info are read only when the associated flags bit is set.
  * It is not necessary to initialize other fields of *info.
diff --git a/src/include/access/transam.h b/src/include/access/transam.h
index 6fa91bfcdc0..55a4ab26b34 100644
--- a/src/include/access/transam.h
+++ b/src/include/access/transam.h
@@ -345,8 +345,6 @@ extern TransactionId TransactionIdLatest(TransactionId mainxid,
 extern XLogRecPtr TransactionIdGetCommitLSN(TransactionId xid);
 
 /* in transam/varsup.c */
-extern Size VarsupShmemSize(void);
-extern void VarsupShmemInit(void);
 extern FullTransactionId GetNewTransactionId(bool isSubXact);
 extern void AdvanceNextFullTransactionIdPastXid(TransactionId xid);
 extern FullTransactionId ReadNextFullTransactionId(void);
diff --git a/src/include/storage/dsm.h b/src/include/storage/dsm.h
index 407657df3ff..1bde71b4406 100644
--- a/src/include/storage/dsm.h
+++ b/src/include/storage/dsm.h
@@ -26,9 +26,6 @@ extern void dsm_postmaster_startup(PGShmemHeader *);
 extern void dsm_backend_shutdown(void);
 extern void dsm_detach_all(void);
 
-extern size_t dsm_estimate_size(void);
-extern void dsm_shmem_init(void);
-
 #ifdef EXEC_BACKEND
 extern void dsm_set_control_handle(dsm_handle h);
 #endif
diff --git a/src/include/storage/dsm_registry.h b/src/include/storage/dsm_registry.h
index 506fae2c9ca..a2269c89f01 100644
--- a/src/include/storage/dsm_registry.h
+++ b/src/include/storage/dsm_registry.h
@@ -22,7 +22,5 @@ extern dsa_area *GetNamedDSA(const char *name, bool *found);
 extern dshash_table *GetNamedDSHash(const char *name,
 									const dshash_parameters *params,
 									bool *found);
-extern Size DSMRegistryShmemSize(void);
-extern void DSMRegistryShmemInit(void);
 
 #endif							/* DSM_REGISTRY_H */
diff --git a/src/include/storage/pg_sema.h b/src/include/storage/pg_sema.h
index 66facc6907a..fe50ee505ba 100644
--- a/src/include/storage/pg_sema.h
+++ b/src/include/storage/pg_sema.h
@@ -37,11 +37,11 @@ typedef HANDLE PGSemaphore;
 #endif
 
 
-/* Report amount of shared memory needed */
-extern Size PGSemaphoreShmemSize(int maxSemas);
+/* Request shared memory needed for semaphores */
+extern void PGSemaphoreShmemRequest(int maxSemas);
 
 /* Module initialization (called during postmaster start or shmem reinit) */
-extern void PGReserveSemaphores(int maxSemas);
+extern void PGSemaphoreInit(int maxSemas);
 
 /* Allocate a PGSemaphore structure with initial count 1 */
 extern PGSemaphore PGSemaphoreCreate(void);
diff --git a/src/include/storage/pmsignal.h b/src/include/storage/pmsignal.h
index 206fb78f8a5..001e6eea61c 100644
--- a/src/include/storage/pmsignal.h
+++ b/src/include/storage/pmsignal.h
@@ -66,8 +66,6 @@ extern PGDLLIMPORT volatile PMSignalData *PMSignalState;
 /*
  * prototypes for functions in pmsignal.c
  */
-extern Size PMSignalShmemSize(void);
-extern void PMSignalShmemInit(void);
 extern void SendPostmasterSignal(PMSignalReason reason);
 extern bool CheckPostmasterSignal(PMSignalReason reason);
 extern void SetQuitSignalReason(QuitSignalReason reason);
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 22822fc68d7..3e1d1fad5f9 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -552,8 +552,6 @@ extern PGDLLIMPORT PGPROC *AuxiliaryProcs;
  * Function Prototypes
  */
 extern int	ProcGlobalSemas(void);
-extern Size ProcGlobalShmemSize(void);
-extern void InitProcGlobal(void);
 extern void InitProcess(void);
 extern void InitProcessPhase2(void);
 extern void InitAuxiliaryProcess(void);
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index abdf021e66e..d718a5b542f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -19,8 +19,6 @@
 #include "utils/snapshot.h"
 
 
-extern Size ProcArrayShmemSize(void);
-extern void ProcArrayShmemInit(void);
 extern void ProcArrayAdd(PGPROC *proc);
 extern void ProcArrayRemove(PGPROC *proc, TransactionId latestXid);
 
diff --git a/src/include/storage/procsignal.h b/src/include/storage/procsignal.h
index cc4f26aa33d..7f855971b5a 100644
--- a/src/include/storage/procsignal.h
+++ b/src/include/storage/procsignal.h
@@ -67,9 +67,6 @@ typedef enum
 /*
  * prototypes for functions in procsignal.c
  */
-extern Size ProcSignalShmemSize(void);
-extern void ProcSignalShmemInit(void);
-
 extern void ProcSignalInit(const uint8 *cancel_key, int cancel_key_len);
 extern int	SendProcSignal(pid_t pid, ProcSignalReason reason,
 						   ProcNumber procNumber);
diff --git a/src/include/storage/sinvaladt.h b/src/include/storage/sinvaladt.h
index 122dbcdf19f..208ea9d051e 100644
--- a/src/include/storage/sinvaladt.h
+++ b/src/include/storage/sinvaladt.h
@@ -27,8 +27,6 @@
 /*
  * prototypes for functions in sinvaladt.c
  */
-extern Size SharedInvalShmemSize(void);
-extern void SharedInvalShmemInit(void);
 extern void SharedInvalBackendInit(bool sendOnly);
 
 extern void SIInsertDataEntries(const SharedInvalidationMessage *data, int n);
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index f0cf01f5a85..d62c29f1361 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -27,4 +27,19 @@
  */
 PG_SHMEM_SUBSYSTEM(LWLockCallbacks)
 
-/* TODO: nothing else for now */
+PG_SHMEM_SUBSYSTEM(dsm_shmem_callbacks)
+PG_SHMEM_SUBSYSTEM(DSMRegistryShmemCallbacks)
+
+/* xlog, clog, and buffers */
+PG_SHMEM_SUBSYSTEM(VarsupShmemCallbacks)
+
+/* process table */
+PG_SHMEM_SUBSYSTEM(ProcGlobalShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(ProcArrayShmemCallbacks)
+
+/* shared-inval messaging */
+PG_SHMEM_SUBSYSTEM(SharedInvalShmemCallbacks)
+
+/* interprocess signaling mechanisms */
+PG_SHMEM_SUBSYSTEM(PMSignalShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(ProcSignalShmemCallbacks)
-- 
2.34.1



  [text/x-patch] v20260405-0010-Convert-SLRUs-to-use-the-new-interface.patch (84.8K, 11-v20260405-0010-Convert-SLRUs-to-use-the-new-interface.patch)
  download | inline diff:
From f0ed619253b094e5ffbde7cd62d3dd060784a445 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Thu, 2 Apr 2026 00:32:45 +0300
Subject: [PATCH v20260405 10/15] Convert SLRUs to use the new interface

I replaced the old SimpleLruInit() function without a backwards
compatibility wrapper, because few extensions define their own SLRUs.
---
 src/backend/access/transam/clog.c        |  55 ++--
 src/backend/access/transam/commit_ts.c   |  85 +++---
 src/backend/access/transam/multixact.c   | 138 +++++----
 src/backend/access/transam/slru.c        | 366 ++++++++++++-----------
 src/backend/access/transam/subtrans.c    |  57 ++--
 src/backend/commands/async.c             | 115 ++++---
 src/backend/storage/ipc/ipci.c           |  16 -
 src/backend/storage/ipc/shmem.c          |   7 +
 src/backend/storage/lmgr/predicate.c     | 266 +++++++---------
 src/backend/utils/activity/pgstat_slru.c |   1 +
 src/include/access/clog.h                |   2 -
 src/include/access/commit_ts.h           |   2 -
 src/include/access/multixact.h           |   2 -
 src/include/access/slru.h                | 112 ++++---
 src/include/access/subtrans.h            |   2 -
 src/include/commands/async.h             |   3 -
 src/include/storage/predicate.h          |   5 -
 src/include/storage/shmem_internal.h     |   1 +
 src/include/storage/subsystemlist.h      |  10 +
 src/test/modules/test_slru/test_slru.c   | 106 +++----
 src/tools/pgindent/typedefs.list         |   4 +-
 21 files changed, 691 insertions(+), 664 deletions(-)

diff --git a/src/backend/access/transam/clog.c b/src/backend/access/transam/clog.c
index c654e0929b3..7cd1a56201f 100644
--- a/src/backend/access/transam/clog.c
+++ b/src/backend/access/transam/clog.c
@@ -43,6 +43,7 @@
 #include "pg_trace.h"
 #include "pgstat.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "storage/sync.h"
 #include "utils/guc_hooks.h"
 #include "utils/wait_event.h"
@@ -106,13 +107,21 @@ TransactionIdToPage(TransactionId xid)
 /*
  * Link to shared-memory data structures for CLOG control
  */
-static SlruCtlData XactCtlData;
+static void CLOGShmemRequest(void *arg);
+static void CLOGShmemInit(void *arg);
+static bool CLOGPagePrecedes(int64 page1, int64 page2);
+static int	clog_errdetail_for_io_error(const void *opaque_data);
 
-#define XactCtl (&XactCtlData)
+const ShmemCallbacks CLOGShmemCallbacks = {
+	.request_fn = CLOGShmemRequest,
+	.init_fn = CLOGShmemInit,
+};
+
+static SlruDesc XactSlruDesc;
+
+#define XactCtl (&XactSlruDesc)
 
 
-static bool CLOGPagePrecedes(int64 page1, int64 page2);
-static int	clog_errdetail_for_io_error(const void *opaque_data);
 static void WriteTruncateXlogRec(int64 pageno, TransactionId oldestXact,
 								 Oid oldestXactDb);
 static void TransactionIdSetPageStatus(TransactionId xid, int nsubxids,
@@ -775,16 +784,10 @@ CLOGShmemBuffers(void)
 }
 
 /*
- * Initialization of shared memory for CLOG
+ * Register shared memory for CLOG
  */
-Size
-CLOGShmemSize(void)
-{
-	return SimpleLruShmemSize(CLOGShmemBuffers(), CLOG_LSNS_PER_PAGE);
-}
-
-void
-CLOGShmemInit(void)
+static void
+CLOGShmemRequest(void *arg)
 {
 	/* If auto-tuning is requested, now is the time to do it */
 	if (transaction_buffers == 0)
@@ -806,12 +809,26 @@ CLOGShmemInit(void)
 							PGC_S_OVERRIDE);
 	}
 	Assert(transaction_buffers != 0);
+	SimpleLruRequest(.desc = &XactSlruDesc,
+					 .name = "transaction",
+					 .Dir = "pg_xact",
+					 .long_segment_names = false,
+
+					 .nslots = CLOGShmemBuffers(),
+					 .nlsns = CLOG_LSNS_PER_PAGE,
+
+					 .sync_handler = SYNC_HANDLER_CLOG,
+					 .PagePrecedes = CLOGPagePrecedes,
+					 .errdetail_for_io_error = clog_errdetail_for_io_error,
 
-	XactCtl->PagePrecedes = CLOGPagePrecedes;
-	XactCtl->errdetail_for_io_error = clog_errdetail_for_io_error;
-	SimpleLruInit(XactCtl, "transaction", CLOGShmemBuffers(), CLOG_LSNS_PER_PAGE,
-				  "pg_xact", LWTRANCHE_XACT_BUFFER,
-				  LWTRANCHE_XACT_SLRU, SYNC_HANDLER_CLOG, false);
+					 .buffer_tranche_id = LWTRANCHE_XACT_BUFFER,
+					 .bank_tranche_id = LWTRANCHE_XACT_SLRU,
+		);
+}
+
+static void
+CLOGShmemInit(void *arg)
+{
 	SlruPagePrecedesUnitTests(XactCtl, CLOG_XACTS_PER_PAGE);
 }
 
@@ -827,7 +844,7 @@ check_transaction_buffers(int *newval, void **extra, GucSource source)
 /*
  * This func must be called ONCE on system install.  It creates
  * the initial CLOG segment.  (The CLOG directory is assumed to
- * have been created by initdb, and CLOGShmemInit must have been
+ * have been created by initdb, and CLOGShmemInit must have been XXX
  * called already.)
  */
 void
diff --git a/src/backend/access/transam/commit_ts.c b/src/backend/access/transam/commit_ts.c
index 36219dd13cc..2625cbf93bf 100644
--- a/src/backend/access/transam/commit_ts.c
+++ b/src/backend/access/transam/commit_ts.c
@@ -30,6 +30,7 @@
 #include "funcapi.h"
 #include "miscadmin.h"
 #include "storage/shmem.h"
+#include "storage/subsystems.h"
 #include "utils/fmgrprotos.h"
 #include "utils/guc_hooks.h"
 #include "utils/timestamp.h"
@@ -80,9 +81,19 @@ TransactionIdToCTsPage(TransactionId xid)
 /*
  * Link to shared-memory data structures for CommitTs control
  */
-static SlruCtlData CommitTsCtlData;
+static void CommitTsShmemRequest(void *arg);
+static void CommitTsShmemInit(void *arg);
+static bool CommitTsPagePrecedes(int64 page1, int64 page2);
+static int	commit_ts_errdetail_for_io_error(const void *opaque_data);
+
+const ShmemCallbacks CommitTsShmemCallbacks = {
+	.request_fn = CommitTsShmemRequest,
+	.init_fn = CommitTsShmemInit,
+};
+
+static SlruDesc CommitTsSlruDesc;
 
-#define CommitTsCtl (&CommitTsCtlData)
+#define CommitTsCtl (&CommitTsSlruDesc)
 
 /*
  * We keep a cache of the last value set in shared memory.
@@ -104,6 +115,7 @@ typedef struct CommitTimestampShared
 
 static CommitTimestampShared *commitTsShared;
 
+static void CommitTsShmemInit(void *arg);
 
 /* GUC variable */
 bool		track_commit_timestamp;
@@ -114,8 +126,6 @@ static void SetXidCommitTsInPage(TransactionId xid, int nsubxids,
 static void TransactionIdSetCommitTs(TransactionId xid, TimestampTz ts,
 									 ReplOriginId nodeid, int slotno);
 static void error_commit_ts_disabled(void);
-static bool CommitTsPagePrecedes(int64 page1, int64 page2);
-static int	commit_ts_errdetail_for_io_error(const void *opaque_data);
 static void ActivateCommitTs(void);
 static void DeactivateCommitTs(void);
 static void WriteTruncateXlogRec(int64 pageno, TransactionId oldestXid);
@@ -512,24 +522,12 @@ CommitTsShmemBuffers(void)
 }
 
 /*
- * Shared memory sizing for CommitTs
+ * Register CommitTs shared memory needs at system startup (postmaster start
+ * or standalone backend)
  */
-Size
-CommitTsShmemSize(void)
-{
-	return SimpleLruShmemSize(CommitTsShmemBuffers(), 0) +
-		sizeof(CommitTimestampShared);
-}
-
-/*
- * Initialize CommitTs at system startup (postmaster start or standalone
- * backend)
- */
-void
-CommitTsShmemInit(void)
+static void
+CommitTsShmemRequest(void *arg)
 {
-	bool		found;
-
 	/* If auto-tuning is requested, now is the time to do it */
 	if (commit_timestamp_buffers == 0)
 	{
@@ -550,31 +548,36 @@ CommitTsShmemInit(void)
 							PGC_S_OVERRIDE);
 	}
 	Assert(commit_timestamp_buffers != 0);
+	SimpleLruRequest(.desc = &CommitTsSlruDesc,
+					 .name = "commit_timestamp",
+					 .Dir = "pg_commit_ts",
+					 .long_segment_names = false,
 
-	CommitTsCtl->PagePrecedes = CommitTsPagePrecedes;
-	CommitTsCtl->errdetail_for_io_error = commit_ts_errdetail_for_io_error;
-	SimpleLruInit(CommitTsCtl, "commit_timestamp", CommitTsShmemBuffers(), 0,
-				  "pg_commit_ts", LWTRANCHE_COMMITTS_BUFFER,
-				  LWTRANCHE_COMMITTS_SLRU,
-				  SYNC_HANDLER_COMMIT_TS,
-				  false);
-	SlruPagePrecedesUnitTests(CommitTsCtl, COMMIT_TS_XACTS_PER_PAGE);
+					 .nslots = CommitTsShmemBuffers(),
 
-	commitTsShared = ShmemInitStruct("CommitTs shared",
-									 sizeof(CommitTimestampShared),
-									 &found);
+					 .PagePrecedes = CommitTsPagePrecedes,
+					 .errdetail_for_io_error = commit_ts_errdetail_for_io_error,
 
-	if (!IsUnderPostmaster)
-	{
-		Assert(!found);
+					 .sync_handler = SYNC_HANDLER_COMMIT_TS,
+					 .buffer_tranche_id = LWTRANCHE_COMMITTS_BUFFER,
+					 .bank_tranche_id = LWTRANCHE_COMMITTS_SLRU,
+		);
 
-		commitTsShared->xidLastCommit = InvalidTransactionId;
-		TIMESTAMP_NOBEGIN(commitTsShared->dataLastCommit.time);
-		commitTsShared->dataLastCommit.nodeid = InvalidReplOriginId;
-		commitTsShared->commitTsActive = false;
-	}
-	else
-		Assert(found);
+	ShmemRequestStruct(.name = "CommitTs shared",
+					   .size = sizeof(CommitTimestampShared),
+					   .ptr = (void **) &commitTsShared,
+		);
+}
+
+static void
+CommitTsShmemInit(void *arg)
+{
+	commitTsShared->xidLastCommit = InvalidTransactionId;
+	TIMESTAMP_NOBEGIN(commitTsShared->dataLastCommit.time);
+	commitTsShared->dataLastCommit.nodeid = InvalidReplOriginId;
+	commitTsShared->commitTsActive = false;
+
+	SlruPagePrecedesUnitTests(CommitTsCtl, COMMIT_TS_XACTS_PER_PAGE);
 }
 
 /*
diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index 9f8d542c098..62d58da4abc 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -83,6 +83,7 @@
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
+#include "storage/subsystems.h"
 #include "utils/guc_hooks.h"
 #include "utils/injection_point.h"
 #include "utils/lsyscache.h"
@@ -113,11 +114,16 @@ PreviousMultiXactId(MultiXactId multi)
 /*
  * Links to shared-memory data structures for MultiXact control
  */
-static SlruCtlData MultiXactOffsetCtlData;
-static SlruCtlData MultiXactMemberCtlData;
+static bool MultiXactOffsetPagePrecedes(int64 page1, int64 page2);
+static int	MultiXactOffsetIoErrorDetail(const void *opaque_data);
+static bool MultiXactMemberPagePrecedes(int64 page1, int64 page2);
+static int	MultiXactMemberIoErrorDetail(const void *opaque_data);
+
+static SlruDesc MultiXactOffsetSlruDesc;
+static SlruDesc MultiXactMemberSlruDesc;
 
-#define MultiXactOffsetCtl	(&MultiXactOffsetCtlData)
-#define MultiXactMemberCtl	(&MultiXactMemberCtlData)
+#define MultiXactOffsetCtl	(&MultiXactOffsetSlruDesc)
+#define MultiXactMemberCtl	(&MultiXactMemberSlruDesc)
 
 /*
  * MultiXact state shared across all backends.  All this state is protected
@@ -220,6 +226,15 @@ static MultiXactStateData *MultiXactState;
 static MultiXactId *OldestMemberMXactId;
 static MultiXactId *OldestVisibleMXactId;
 
+static void MultiXactShmemRequest(void *arg);
+static void MultiXactShmemInit(void *arg);
+static void MultiXactShmemAttach(void *arg);
+
+const ShmemCallbacks MultiXactShmemCallbacks = {
+	.request_fn = MultiXactShmemRequest,
+	.init_fn = MultiXactShmemInit,
+	.attach_fn = MultiXactShmemAttach,
+};
 
 static inline MultiXactId *
 MyOldestMemberMXactIdSlot(void)
@@ -321,10 +336,6 @@ typedef struct MultiXactMemberSlruReadContext
 	MultiXactOffset offset;
 } MultiXactMemberSlruReadContext;
 
-static bool MultiXactOffsetPagePrecedes(int64 page1, int64 page2);
-static bool MultiXactMemberPagePrecedes(int64 page1, int64 page2);
-static int	MultiXactOffsetIoErrorDetail(const void *opaque_data);
-static int	MultiXactMemberIoErrorDetail(const void *opaque_data);
 static void ExtendMultiXactOffset(MultiXactId multi);
 static void ExtendMultiXactMember(MultiXactOffset offset, int nmembers);
 static void SetOldestOffset(void);
@@ -1747,80 +1758,81 @@ multixact_twophase_postabort(FullTransactionId fxid, uint16 info,
 	multixact_twophase_postcommit(fxid, info, recdata, len);
 }
 
+
 /*
- * Initialization of shared memory for MultiXact.
- *
- * MultiXactSharedStateShmemSize() calculates the size of the MultiXactState
- * struct, and the two per-backend MultiXactId arrays.  They are carved out of
- * the same allocation.  MultiXactShmemSize() additionally includes the memory
- * needed for the two SLRU areas.
+ * Register shared memory needs for MultiXact.
  */
-static Size
-MultiXactSharedStateShmemSize(void)
+static void
+MultiXactShmemRequest(void *arg)
 {
 	Size		size;
 
+	/*
+	 * Calculate the size of the MultiXactState struct, and the two
+	 * per-backend MultiXactId arrays.  They are carved out of the same
+	 * allocation.
+	 */
 	size = offsetof(MultiXactStateData, perBackendXactIds);
 	size = add_size(size,
 					mul_size(sizeof(MultiXactId), NumMemberSlots));
 	size = add_size(size,
 					mul_size(sizeof(MultiXactId), NumVisibleSlots));
-	return size;
-}
+	ShmemRequestStruct(.name = "Shared MultiXact State",
+					   .size = size,
+					   .ptr = (void **) &MultiXactState,
+		);
 
-Size
-MultiXactShmemSize(void)
-{
-	Size		size;
+	SimpleLruRequest(.desc = &MultiXactOffsetSlruDesc,
+					 .name = "multixact_offset",
+					 .Dir = "pg_multixact/offsets",
+					 .long_segment_names = false,
 
-	size = MultiXactSharedStateShmemSize();
-	size = add_size(size, SimpleLruShmemSize(multixact_offset_buffers, 0));
-	size = add_size(size, SimpleLruShmemSize(multixact_member_buffers, 0));
+					 .nslots = multixact_offset_buffers,
 
-	return size;
-}
+					 .sync_handler = SYNC_HANDLER_MULTIXACT_OFFSET,
+					 .PagePrecedes = MultiXactOffsetPagePrecedes,
+					 .errdetail_for_io_error = MultiXactOffsetIoErrorDetail,
 
-void
-MultiXactShmemInit(void)
-{
-	bool		found;
+					 .buffer_tranche_id = LWTRANCHE_MULTIXACTOFFSET_BUFFER,
+					 .bank_tranche_id = LWTRANCHE_MULTIXACTOFFSET_SLRU,
+		);
 
-	debug_elog2(DEBUG2, "Shared Memory Init for MultiXact");
+	SimpleLruRequest(.desc = &MultiXactMemberSlruDesc,
+					 .name = "multixact_member",
+					 .Dir = "pg_multixact/members",
+					 .long_segment_names = true,
 
-	MultiXactOffsetCtl->PagePrecedes = MultiXactOffsetPagePrecedes;
-	MultiXactMemberCtl->PagePrecedes = MultiXactMemberPagePrecedes;
-	MultiXactOffsetCtl->errdetail_for_io_error = MultiXactOffsetIoErrorDetail;
-	MultiXactMemberCtl->errdetail_for_io_error = MultiXactMemberIoErrorDetail;
+					 .nslots = multixact_member_buffers,
 
-	SimpleLruInit(MultiXactOffsetCtl,
-				  "multixact_offset", multixact_offset_buffers, 0,
-				  "pg_multixact/offsets", LWTRANCHE_MULTIXACTOFFSET_BUFFER,
-				  LWTRANCHE_MULTIXACTOFFSET_SLRU,
-				  SYNC_HANDLER_MULTIXACT_OFFSET,
-				  false);
-	SlruPagePrecedesUnitTests(MultiXactOffsetCtl, MULTIXACT_OFFSETS_PER_PAGE);
-	SimpleLruInit(MultiXactMemberCtl,
-				  "multixact_member", multixact_member_buffers, 0,
-				  "pg_multixact/members", LWTRANCHE_MULTIXACTMEMBER_BUFFER,
-				  LWTRANCHE_MULTIXACTMEMBER_SLRU,
-				  SYNC_HANDLER_MULTIXACT_MEMBER,
-				  true);
-	/* doesn't call SimpleLruTruncate() or meet criteria for unit tests */
-
-	/* Initialize our shared state struct */
-	MultiXactState = ShmemInitStruct("Shared MultiXact State",
-									 MultiXactSharedStateShmemSize(),
-									 &found);
-	if (!IsUnderPostmaster)
-	{
-		Assert(!found);
+					 .sync_handler = SYNC_HANDLER_MULTIXACT_MEMBER,
+					 .PagePrecedes = MultiXactMemberPagePrecedes,
+					 .errdetail_for_io_error = MultiXactMemberIoErrorDetail,
 
-		/* Make sure we zero out the per-backend state */
-		MemSet(MultiXactState, 0, MultiXactSharedStateShmemSize());
-	}
-	else
-		Assert(found);
+					 .buffer_tranche_id = LWTRANCHE_MULTIXACTMEMBER_BUFFER,
+					 .bank_tranche_id = LWTRANCHE_MULTIXACTMEMBER_SLRU,
+		);
 
+	/*
+	 * members SLRU doesn't call SimpleLruTruncate() or meet criteria for unit
+	 * tests
+	 */
+}
+
+static void
+MultiXactShmemInit(void *arg)
+{
+	/*
+	 * Set up array pointers.
+	 */
+	OldestMemberMXactId = MultiXactState->perBackendXactIds;
+	OldestVisibleMXactId = OldestMemberMXactId + NumMemberSlots;
+
+	SlruPagePrecedesUnitTests(MultiXactOffsetCtl, MULTIXACT_OFFSETS_PER_PAGE);
+}
+
+static void
+MultiXactShmemAttach(void *arg)
+{
 	/*
 	 * Set up array pointers.
 	 */
diff --git a/src/backend/access/transam/slru.c b/src/backend/access/transam/slru.c
index a2bb8fa8033..47dd52d6749 100644
--- a/src/backend/access/transam/slru.c
+++ b/src/backend/access/transam/slru.c
@@ -70,7 +70,9 @@
 #include "pgstat.h"
 #include "storage/fd.h"
 #include "storage/shmem.h"
+#include "storage/shmem_internal.h"
 #include "utils/guc.h"
+#include "utils/memutils.h"
 #include "utils/wait_event.h"
 
 /*
@@ -89,9 +91,9 @@
  *  dir/123456 for [2^20, 2^24-1]
  */
 static inline int
-SlruFileName(SlruCtl ctl, char *path, int64 segno)
+SlruFileName(SlruDesc *ctl, char *path, int64 segno)
 {
-	if (ctl->long_segment_names)
+	if (ctl->options.long_segment_names)
 	{
 		/*
 		 * We could use 16 characters here but the disadvantage would be that
@@ -101,7 +103,7 @@ SlruFileName(SlruCtl ctl, char *path, int64 segno)
 		 * that in the future we can't decrease SLRU_PAGES_PER_SEGMENT easily.
 		 */
 		Assert(segno >= 0 && segno <= INT64CONST(0xFFFFFFFFFFFFFFF));
-		return snprintf(path, MAXPGPATH, "%s/%015" PRIX64, ctl->Dir, segno);
+		return snprintf(path, MAXPGPATH, "%s/%015" PRIX64, ctl->options.Dir, segno);
 	}
 	else
 	{
@@ -110,7 +112,7 @@ SlruFileName(SlruCtl ctl, char *path, int64 segno)
 		 * integers are allowed. See SlruCorrectSegmentFilenameLength()
 		 */
 		Assert(segno >= 0 && segno <= INT64CONST(0xFFFFFF));
-		return snprintf(path, MAXPGPATH, "%s/%04X", (ctl)->Dir,
+		return snprintf(path, MAXPGPATH, "%s/%04X", (ctl)->options.Dir,
 						(unsigned int) segno);
 	}
 }
@@ -176,19 +178,19 @@ static SlruErrorCause slru_errcause;
 static int	slru_errno;
 
 
-static void SimpleLruZeroLSNs(SlruCtl ctl, int slotno);
-static void SimpleLruWaitIO(SlruCtl ctl, int slotno);
-static void SlruInternalWritePage(SlruCtl ctl, int slotno, SlruWriteAll fdata);
-static bool SlruPhysicalReadPage(SlruCtl ctl, int64 pageno, int slotno);
-static bool SlruPhysicalWritePage(SlruCtl ctl, int64 pageno, int slotno,
+static void SimpleLruZeroLSNs(SlruDesc *ctl, int slotno);
+static void SimpleLruWaitIO(SlruDesc *ctl, int slotno);
+static void SlruInternalWritePage(SlruDesc *ctl, int slotno, SlruWriteAll fdata);
+static bool SlruPhysicalReadPage(SlruDesc *ctl, int64 pageno, int slotno);
+static bool SlruPhysicalWritePage(SlruDesc *ctl, int64 pageno, int slotno,
 								  SlruWriteAll fdata);
-static void SlruReportIOError(SlruCtl ctl, int64 pageno,
+static void SlruReportIOError(SlruDesc *ctl, int64 pageno,
 							  const void *opaque_data);
-static int	SlruSelectLRUPage(SlruCtl ctl, int64 pageno);
+static int	SlruSelectLRUPage(SlruDesc *ctl, int64 pageno);
 
-static bool SlruScanDirCbDeleteCutoff(SlruCtl ctl, char *filename,
+static bool SlruScanDirCbDeleteCutoff(SlruDesc *ctl, char *filename,
 									  int64 segpage, void *data);
-static void SlruInternalDeleteSegment(SlruCtl ctl, int64 segno);
+static void SlruInternalDeleteSegment(SlruDesc *ctl, int64 segno);
 static inline void SlruRecentlyUsed(SlruShared shared, int slotno);
 
 
@@ -196,7 +198,7 @@ static inline void SlruRecentlyUsed(SlruShared shared, int slotno);
  * Initialization of shared memory
  */
 
-Size
+static Size
 SimpleLruShmemSize(int nslots, int nlsns)
 {
 	int			nbanks = nslots / SLRU_BANK_SIZE;
@@ -238,120 +240,135 @@ SimpleLruAutotuneBuffers(int divisor, int max)
 }
 
 /*
- * Initialize, or attach to, a simple LRU cache in shared memory.
- *
- * ctl: address of local (unshared) control structure.
- * name: name of SLRU.  (This is user-visible, pick with care!)
- * nslots: number of page slots to use.
- * nlsns: number of LSN groups per page (set to zero if not relevant).
- * subdir: PGDATA-relative subdirectory that will contain the files.
- * buffer_tranche_id: tranche ID to use for the SLRU's per-buffer LWLocks.
- * bank_tranche_id: tranche ID to use for the bank LWLocks.
- * sync_handler: which set of functions to use to handle sync requests
- * long_segment_names: use short or long segment names
+ * Register a simple LRU cache in shared memory.
  */
 void
-SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns,
-			  const char *subdir, int buffer_tranche_id, int bank_tranche_id,
-			  SyncRequestHandler sync_handler, bool long_segment_names)
+SimpleLruRequestWithOpts(const SlruOpts *options)
 {
+	SlruOpts   *options_copy;
+
+	Assert(options->name != NULL);
+	Assert(options->nslots > 0);
+	Assert(options->PagePrecedes != NULL);
+	Assert(options->errdetail_for_io_error != NULL);
+
+	options_copy = MemoryContextAlloc(TopMemoryContext,
+									  sizeof(SlruOpts));
+	memcpy(options_copy, options, sizeof(SlruOpts));
+
+	options_copy->base.name = options->name;
+	options_copy->base.size = SimpleLruShmemSize(options_copy->nslots, options_copy->nlsns);
+
+	ShmemRequestInternal(&options_copy->base, SHMEM_KIND_SLRU);
+}
+
+/* Initialize locks and shared memory area */
+void
+shmem_slru_init(void *location, ShmemStructOpts *base_options)
+{
+	SlruOpts   *options = (SlruOpts *) base_options;
+	SlruDesc   *desc = (SlruDesc *) options->desc;
+	char		namebuf[NAMEDATALEN];
 	SlruShared	shared;
-	bool		found;
+	int			nslots = options->nslots;
 	int			nbanks = nslots / SLRU_BANK_SIZE;
+	int			nlsns = options->nlsns;
+	char	   *ptr;
+	Size		offset;
+
+	shared = (SlruShared) location;
+	desc->shared = shared;
+	desc->nbanks = nbanks;
+	memcpy(&desc->options, options, sizeof(SlruOpts));
+
+	/* assign new tranche IDs, if not given */
+	if (desc->options.buffer_tranche_id == 0)
+	{
+		snprintf(namebuf, sizeof(namebuf), "%s buffer", desc->options.name);
+		desc->options.buffer_tranche_id = LWLockNewTrancheId(namebuf);
+	}
+	if (desc->options.bank_tranche_id == 0)
+	{
+		snprintf(namebuf, sizeof(namebuf), "%s bank", desc->options.name);
+		desc->options.bank_tranche_id = LWLockNewTrancheId(namebuf);
+	}
 
 	Assert(nslots <= SLRU_MAX_ALLOWED_BUFFERS);
 
-	Assert(ctl->PagePrecedes != NULL);
-	Assert(ctl->errdetail_for_io_error != NULL);
+	memset(shared, 0, sizeof(SlruSharedData));
 
-	shared = (SlruShared) ShmemInitStruct(name,
-										  SimpleLruShmemSize(nslots, nlsns),
-										  &found);
+	shared->num_slots = nslots;
+	shared->lsn_groups_per_page = nlsns;
 
-	if (!IsUnderPostmaster)
-	{
-		/* Initialize locks and shared memory area */
-		char	   *ptr;
-		Size		offset;
-
-		Assert(!found);
-
-		memset(shared, 0, sizeof(SlruSharedData));
-
-		shared->num_slots = nslots;
-		shared->lsn_groups_per_page = nlsns;
-
-		pg_atomic_init_u64(&shared->latest_page_number, 0);
-
-		shared->slru_stats_idx = pgstat_get_slru_index(name);
-
-		ptr = (char *) shared;
-		offset = MAXALIGN(sizeof(SlruSharedData));
-		shared->page_buffer = (char **) (ptr + offset);
-		offset += MAXALIGN(nslots * sizeof(char *));
-		shared->page_status = (SlruPageStatus *) (ptr + offset);
-		offset += MAXALIGN(nslots * sizeof(SlruPageStatus));
-		shared->page_dirty = (bool *) (ptr + offset);
-		offset += MAXALIGN(nslots * sizeof(bool));
-		shared->page_number = (int64 *) (ptr + offset);
-		offset += MAXALIGN(nslots * sizeof(int64));
-		shared->page_lru_count = (int *) (ptr + offset);
-		offset += MAXALIGN(nslots * sizeof(int));
-
-		/* Initialize LWLocks */
-		shared->buffer_locks = (LWLockPadded *) (ptr + offset);
-		offset += MAXALIGN(nslots * sizeof(LWLockPadded));
-		shared->bank_locks = (LWLockPadded *) (ptr + offset);
-		offset += MAXALIGN(nbanks * sizeof(LWLockPadded));
-		shared->bank_cur_lru_count = (int *) (ptr + offset);
-		offset += MAXALIGN(nbanks * sizeof(int));
-
-		if (nlsns > 0)
-		{
-			shared->group_lsn = (XLogRecPtr *) (ptr + offset);
-			offset += MAXALIGN(nslots * nlsns * sizeof(XLogRecPtr));
-		}
+	pg_atomic_init_u64(&shared->latest_page_number, 0);
 
-		ptr += BUFFERALIGN(offset);
-		for (int slotno = 0; slotno < nslots; slotno++)
-		{
-			LWLockInitialize(&shared->buffer_locks[slotno].lock,
-							 buffer_tranche_id);
+	shared->slru_stats_idx = pgstat_get_slru_index(desc->options.name);
 
-			shared->page_buffer[slotno] = ptr;
-			shared->page_status[slotno] = SLRU_PAGE_EMPTY;
-			shared->page_dirty[slotno] = false;
-			shared->page_lru_count[slotno] = 0;
-			ptr += BLCKSZ;
-		}
+	ptr = (char *) shared;
+	offset = MAXALIGN(sizeof(SlruSharedData));
+	shared->page_buffer = (char **) (ptr + offset);
+	offset += MAXALIGN(nslots * sizeof(char *));
+	shared->page_status = (SlruPageStatus *) (ptr + offset);
+	offset += MAXALIGN(nslots * sizeof(SlruPageStatus));
+	shared->page_dirty = (bool *) (ptr + offset);
+	offset += MAXALIGN(nslots * sizeof(bool));
+	shared->page_number = (int64 *) (ptr + offset);
+	offset += MAXALIGN(nslots * sizeof(int64));
+	shared->page_lru_count = (int *) (ptr + offset);
+	offset += MAXALIGN(nslots * sizeof(int));
 
-		/* Initialize the slot banks. */
-		for (int bankno = 0; bankno < nbanks; bankno++)
-		{
-			LWLockInitialize(&shared->bank_locks[bankno].lock, bank_tranche_id);
-			shared->bank_cur_lru_count[bankno] = 0;
-		}
+	/* Initialize LWLocks */
+	shared->buffer_locks = (LWLockPadded *) (ptr + offset);
+	offset += MAXALIGN(nslots * sizeof(LWLockPadded));
+	shared->bank_locks = (LWLockPadded *) (ptr + offset);
+	offset += MAXALIGN(nbanks * sizeof(LWLockPadded));
+	shared->bank_cur_lru_count = (int *) (ptr + offset);
+	offset += MAXALIGN(nbanks * sizeof(int));
 
-		/* Should fit to estimated shmem size */
-		Assert(ptr - (char *) shared <= SimpleLruShmemSize(nslots, nlsns));
+	if (nlsns > 0)
+	{
+		shared->group_lsn = (XLogRecPtr *) (ptr + offset);
+		offset += MAXALIGN(nslots * nlsns * sizeof(XLogRecPtr));
 	}
-	else
+
+	ptr += BUFFERALIGN(offset);
+	for (int slotno = 0; slotno < nslots; slotno++)
 	{
-		Assert(found);
-		Assert(shared->num_slots == nslots);
+		LWLockInitialize(&shared->buffer_locks[slotno].lock,
+						 desc->options.buffer_tranche_id);
+
+		shared->page_buffer[slotno] = ptr;
+		shared->page_status[slotno] = SLRU_PAGE_EMPTY;
+		shared->page_dirty[slotno] = false;
+		shared->page_lru_count[slotno] = 0;
+		ptr += BLCKSZ;
 	}
 
-	/*
-	 * Initialize the unshared control struct, including directory path. We
-	 * assume caller set PagePrecedes.
-	 */
-	ctl->shared = shared;
-	ctl->sync_handler = sync_handler;
-	ctl->long_segment_names = long_segment_names;
-	ctl->nbanks = nbanks;
-	strlcpy(ctl->Dir, subdir, sizeof(ctl->Dir));
+	/* Initialize the slot banks. */
+	for (int bankno = 0; bankno < nbanks; bankno++)
+	{
+		LWLockInitialize(&shared->bank_locks[bankno].lock, desc->options.bank_tranche_id);
+		shared->bank_cur_lru_count[bankno] = 0;
+	}
+
+	/* Should fit to estimated shmem size */
+	Assert(ptr - (char *) shared <= SimpleLruShmemSize(nslots, nlsns));
+}
+
+void
+shmem_slru_attach(void *location, ShmemStructOpts *base_options)
+{
+	SlruOpts   *options = (SlruOpts *) base_options;
+	SlruDesc   *desc = (SlruDesc *) options->desc;
+	int			nslots = options->nslots;
+	int			nbanks = nslots / SLRU_BANK_SIZE;
+
+	desc->shared = (SlruShared) location;
+	desc->nbanks = nbanks;
+	memcpy(&desc->options, options, sizeof(SlruOpts));
 }
 
+
 /*
  * Helper function for GUC check_hook to check whether slru buffers are in
  * multiples of SLRU_BANK_SIZE.
@@ -377,7 +394,7 @@ check_slru_buffers(const char *name, int *newval)
  * Bank lock must be held at entry, and will be held at exit.
  */
 int
-SimpleLruZeroPage(SlruCtl ctl, int64 pageno)
+SimpleLruZeroPage(SlruDesc *ctl, int64 pageno)
 {
 	SlruShared	shared = ctl->shared;
 	int			slotno;
@@ -430,7 +447,7 @@ SimpleLruZeroPage(SlruCtl ctl, int64 pageno)
  * This assumes that InvalidXLogRecPtr is bitwise-all-0.
  */
 static void
-SimpleLruZeroLSNs(SlruCtl ctl, int slotno)
+SimpleLruZeroLSNs(SlruDesc *ctl, int slotno)
 {
 	SlruShared	shared = ctl->shared;
 
@@ -446,7 +463,7 @@ SimpleLruZeroLSNs(SlruCtl ctl, int slotno)
  * SLRU bank lock is acquired and released here.
  */
 void
-SimpleLruZeroAndWritePage(SlruCtl ctl, int64 pageno)
+SimpleLruZeroAndWritePage(SlruDesc *ctl, int64 pageno)
 {
 	int			slotno;
 	LWLock	   *lock;
@@ -472,7 +489,7 @@ SimpleLruZeroAndWritePage(SlruCtl ctl, int64 pageno)
  * Bank lock must be held at entry, and will be held at exit.
  */
 static void
-SimpleLruWaitIO(SlruCtl ctl, int slotno)
+SimpleLruWaitIO(SlruDesc *ctl, int slotno)
 {
 	SlruShared	shared = ctl->shared;
 	int			bankno = SlotGetBankNumber(slotno);
@@ -530,7 +547,7 @@ SimpleLruWaitIO(SlruCtl ctl, int slotno)
  * The correct bank lock must be held at entry, and will be held at exit.
  */
 int
-SimpleLruReadPage(SlruCtl ctl, int64 pageno, bool write_ok,
+SimpleLruReadPage(SlruDesc *ctl, int64 pageno, bool write_ok,
 				  const void *opaque_data)
 {
 	SlruShared	shared = ctl->shared;
@@ -634,7 +651,7 @@ SimpleLruReadPage(SlruCtl ctl, int64 pageno, bool write_ok,
  * It is unspecified whether the lock will be shared or exclusive.
  */
 int
-SimpleLruReadPage_ReadOnly(SlruCtl ctl, int64 pageno, const void *opaque_data)
+SimpleLruReadPage_ReadOnly(SlruDesc *ctl, int64 pageno, const void *opaque_data)
 {
 	SlruShared	shared = ctl->shared;
 	LWLock	   *banklock = SimpleLruGetBankLock(ctl, pageno);
@@ -681,7 +698,7 @@ SimpleLruReadPage_ReadOnly(SlruCtl ctl, int64 pageno, const void *opaque_data)
  * Bank lock must be held at entry, and will be held at exit.
  */
 static void
-SlruInternalWritePage(SlruCtl ctl, int slotno, SlruWriteAll fdata)
+SlruInternalWritePage(SlruDesc *ctl, int slotno, SlruWriteAll fdata)
 {
 	SlruShared	shared = ctl->shared;
 	int64		pageno = shared->page_number[slotno];
@@ -761,7 +778,7 @@ SlruInternalWritePage(SlruCtl ctl, int slotno, SlruWriteAll fdata)
  * fdata is always passed a NULL here.
  */
 void
-SimpleLruWritePage(SlruCtl ctl, int slotno)
+SimpleLruWritePage(SlruDesc *ctl, int slotno)
 {
 	Assert(ctl->shared->page_status[slotno] != SLRU_PAGE_EMPTY);
 
@@ -775,7 +792,7 @@ SimpleLruWritePage(SlruCtl ctl, int slotno)
  * large enough to contain the given page.
  */
 bool
-SimpleLruDoesPhysicalPageExist(SlruCtl ctl, int64 pageno)
+SimpleLruDoesPhysicalPageExist(SlruDesc *ctl, int64 pageno)
 {
 	int64		segno = pageno / SLRU_PAGES_PER_SEGMENT;
 	int			rpageno = pageno % SLRU_PAGES_PER_SEGMENT;
@@ -833,7 +850,7 @@ SimpleLruDoesPhysicalPageExist(SlruCtl ctl, int64 pageno)
  * read/write operations.  We could cache one virtual file pointer ...
  */
 static bool
-SlruPhysicalReadPage(SlruCtl ctl, int64 pageno, int slotno)
+SlruPhysicalReadPage(SlruDesc *ctl, int64 pageno, int slotno)
 {
 	SlruShared	shared = ctl->shared;
 	int64		segno = pageno / SLRU_PAGES_PER_SEGMENT;
@@ -905,7 +922,7 @@ SlruPhysicalReadPage(SlruCtl ctl, int64 pageno, int slotno)
  * SimpleLruWriteAll.
  */
 static bool
-SlruPhysicalWritePage(SlruCtl ctl, int64 pageno, int slotno, SlruWriteAll fdata)
+SlruPhysicalWritePage(SlruDesc *ctl, int64 pageno, int slotno, SlruWriteAll fdata)
 {
 	SlruShared	shared = ctl->shared;
 	int64		segno = pageno / SLRU_PAGES_PER_SEGMENT;
@@ -1037,11 +1054,11 @@ SlruPhysicalWritePage(SlruCtl ctl, int64 pageno, int slotno, SlruWriteAll fdata)
 	pgstat_report_wait_end();
 
 	/* Queue up a sync request for the checkpointer. */
-	if (ctl->sync_handler != SYNC_HANDLER_NONE)
+	if (ctl->options.sync_handler != SYNC_HANDLER_NONE)
 	{
 		FileTag		tag;
 
-		INIT_SLRUFILETAG(tag, ctl->sync_handler, segno);
+		INIT_SLRUFILETAG(tag, ctl->options.sync_handler, segno);
 		if (!RegisterSyncRequest(&tag, SYNC_REQUEST, false))
 		{
 			/* No space to enqueue sync request.  Do it synchronously. */
@@ -1077,7 +1094,7 @@ SlruPhysicalWritePage(SlruCtl ctl, int64 pageno, int slotno, SlruWriteAll fdata)
  * SlruPhysicalWritePage.  Call this after cleaning up shared-memory state.
  */
 static void
-SlruReportIOError(SlruCtl ctl, int64 pageno, const void *opaque_data)
+SlruReportIOError(SlruDesc *ctl, int64 pageno, const void *opaque_data)
 {
 	int64		segno = pageno / SLRU_PAGES_PER_SEGMENT;
 	int			rpageno = pageno % SLRU_PAGES_PER_SEGMENT;
@@ -1092,14 +1109,14 @@ SlruReportIOError(SlruCtl ctl, int64 pageno, const void *opaque_data)
 			ereport(ERROR,
 					(errcode_for_file_access(),
 					 errmsg("could not open file \"%s\": %m", path),
-					 opaque_data ? ctl->errdetail_for_io_error(opaque_data) : 0));
+					 opaque_data ? ctl->options.errdetail_for_io_error(opaque_data) : 0));
 			break;
 		case SLRU_SEEK_FAILED:
 			ereport(ERROR,
 					(errcode_for_file_access(),
 					 errmsg("could not seek in file \"%s\" to offset %d: %m",
 							path, offset),
-					 opaque_data ? ctl->errdetail_for_io_error(opaque_data) : 0));
+					 opaque_data ? ctl->options.errdetail_for_io_error(opaque_data) : 0));
 			break;
 		case SLRU_READ_FAILED:
 			if (errno)
@@ -1107,12 +1124,12 @@ SlruReportIOError(SlruCtl ctl, int64 pageno, const void *opaque_data)
 						(errcode_for_file_access(),
 						 errmsg("could not read from file \"%s\" at offset %d: %m",
 								path, offset),
-						 opaque_data ? ctl->errdetail_for_io_error(opaque_data) : 0));
+						 opaque_data ? ctl->options.errdetail_for_io_error(opaque_data) : 0));
 			else
 				ereport(ERROR,
 						(errmsg("could not read from file \"%s\" at offset %d: read too few bytes",
 								path, offset),
-						 opaque_data ? ctl->errdetail_for_io_error(opaque_data) : 0));
+						 opaque_data ? ctl->options.errdetail_for_io_error(opaque_data) : 0));
 			break;
 		case SLRU_WRITE_FAILED:
 			if (errno)
@@ -1120,26 +1137,26 @@ SlruReportIOError(SlruCtl ctl, int64 pageno, const void *opaque_data)
 						(errcode_for_file_access(),
 						 errmsg("Could not write to file \"%s\" at offset %d: %m",
 								path, offset),
-						 opaque_data ? ctl->errdetail_for_io_error(opaque_data) : 0));
+						 opaque_data ? ctl->options.errdetail_for_io_error(opaque_data) : 0));
 			else
 				ereport(ERROR,
 						(errmsg("Could not write to file \"%s\" at offset %d: wrote too few bytes.",
 								path, offset),
-						 opaque_data ? ctl->errdetail_for_io_error(opaque_data) : 0));
+						 opaque_data ? ctl->options.errdetail_for_io_error(opaque_data) : 0));
 			break;
 		case SLRU_FSYNC_FAILED:
 			ereport(data_sync_elevel(ERROR),
 					(errcode_for_file_access(),
 					 errmsg("could not fsync file \"%s\": %m",
 							path),
-					 opaque_data ? ctl->errdetail_for_io_error(opaque_data) : 0));
+					 opaque_data ? ctl->options.errdetail_for_io_error(opaque_data) : 0));
 			break;
 		case SLRU_CLOSE_FAILED:
 			ereport(ERROR,
 					(errcode_for_file_access(),
 					 errmsg("could not close file \"%s\": %m",
 							path),
-					 opaque_data ? ctl->errdetail_for_io_error(opaque_data) : 0));
+					 opaque_data ? ctl->options.errdetail_for_io_error(opaque_data) : 0));
 			break;
 		default:
 			/* can't get here, we trust */
@@ -1199,7 +1216,7 @@ SlruRecentlyUsed(SlruShared shared, int slotno)
  * The correct bank lock must be held at entry, and will be held at exit.
  */
 static int
-SlruSelectLRUPage(SlruCtl ctl, int64 pageno)
+SlruSelectLRUPage(SlruDesc *ctl, int64 pageno)
 {
 	SlruShared	shared = ctl->shared;
 
@@ -1291,8 +1308,8 @@ SlruSelectLRUPage(SlruCtl ctl, int64 pageno)
 			{
 				if (this_delta > best_valid_delta ||
 					(this_delta == best_valid_delta &&
-					 ctl->PagePrecedes(this_page_number,
-									   best_valid_page_number)))
+					 ctl->options.PagePrecedes(this_page_number,
+											   best_valid_page_number)))
 				{
 					bestvalidslot = slotno;
 					best_valid_delta = this_delta;
@@ -1303,8 +1320,8 @@ SlruSelectLRUPage(SlruCtl ctl, int64 pageno)
 			{
 				if (this_delta > best_invalid_delta ||
 					(this_delta == best_invalid_delta &&
-					 ctl->PagePrecedes(this_page_number,
-									   best_invalid_page_number)))
+					 ctl->options.PagePrecedes(this_page_number,
+											   best_invalid_page_number)))
 				{
 					bestinvalidslot = slotno;
 					best_invalid_delta = this_delta;
@@ -1352,7 +1369,7 @@ SlruSelectLRUPage(SlruCtl ctl, int64 pageno)
  * entries are on disk.
  */
 void
-SimpleLruWriteAll(SlruCtl ctl, bool allow_redirtied)
+SimpleLruWriteAll(SlruDesc *ctl, bool allow_redirtied)
 {
 	SlruShared	shared = ctl->shared;
 	SlruWriteAllData fdata;
@@ -1422,8 +1439,8 @@ SimpleLruWriteAll(SlruCtl ctl, bool allow_redirtied)
 		SlruReportIOError(ctl, pageno, NULL);
 
 	/* Ensure that directory entries for new files are on disk. */
-	if (ctl->sync_handler != SYNC_HANDLER_NONE)
-		fsync_fname(ctl->Dir, true);
+	if (ctl->options.sync_handler != SYNC_HANDLER_NONE)
+		fsync_fname(ctl->options.Dir, true);
 }
 
 /*
@@ -1438,7 +1455,7 @@ SimpleLruWriteAll(SlruCtl ctl, bool allow_redirtied)
  * after it has accrued freshly-written data.
  */
 void
-SimpleLruTruncate(SlruCtl ctl, int64 cutoffPage)
+SimpleLruTruncate(SlruDesc *ctl, int64 cutoffPage)
 {
 	SlruShared	shared = ctl->shared;
 	int			prevbank;
@@ -1460,12 +1477,12 @@ restart:
 	 * bugs elsewhere in SLRU handling, so we don't care if we read a slightly
 	 * outdated value; therefore we don't add a memory barrier.
 	 */
-	if (ctl->PagePrecedes(pg_atomic_read_u64(&shared->latest_page_number),
-						  cutoffPage))
+	if (ctl->options.PagePrecedes(pg_atomic_read_u64(&shared->latest_page_number),
+								  cutoffPage))
 	{
 		ereport(LOG,
 				(errmsg("could not truncate directory \"%s\": apparent wraparound",
-						ctl->Dir)));
+						ctl->options.Dir)));
 		return;
 	}
 
@@ -1488,7 +1505,7 @@ restart:
 
 		if (shared->page_status[slotno] == SLRU_PAGE_EMPTY)
 			continue;
-		if (!ctl->PagePrecedes(shared->page_number[slotno], cutoffPage))
+		if (!ctl->options.PagePrecedes(shared->page_number[slotno], cutoffPage))
 			continue;
 
 		/*
@@ -1533,16 +1550,16 @@ restart:
  * they either can't yet contain anything, or have already been cleaned out.
  */
 static void
-SlruInternalDeleteSegment(SlruCtl ctl, int64 segno)
+SlruInternalDeleteSegment(SlruDesc *ctl, int64 segno)
 {
 	char		path[MAXPGPATH];
 
 	/* Forget any fsync requests queued for this segment. */
-	if (ctl->sync_handler != SYNC_HANDLER_NONE)
+	if (ctl->options.sync_handler != SYNC_HANDLER_NONE)
 	{
 		FileTag		tag;
 
-		INIT_SLRUFILETAG(tag, ctl->sync_handler, segno);
+		INIT_SLRUFILETAG(tag, ctl->options.sync_handler, segno);
 		RegisterSyncRequest(&tag, SYNC_FORGET_REQUEST, true);
 	}
 
@@ -1556,7 +1573,7 @@ SlruInternalDeleteSegment(SlruCtl ctl, int64 segno)
  * Delete an individual SLRU segment, identified by the segment number.
  */
 void
-SlruDeleteSegment(SlruCtl ctl, int64 segno)
+SlruDeleteSegment(SlruDesc *ctl, int64 segno)
 {
 	SlruShared	shared = ctl->shared;
 	int			prevbank = SlotGetBankNumber(0);
@@ -1633,19 +1650,19 @@ restart:
  * first>=cutoff && last>=cutoff: no; every page of this segment is too young
  */
 static bool
-SlruMayDeleteSegment(SlruCtl ctl, int64 segpage, int64 cutoffPage)
+SlruMayDeleteSegment(SlruDesc *ctl, int64 segpage, int64 cutoffPage)
 {
 	int64		seg_last_page = segpage + SLRU_PAGES_PER_SEGMENT - 1;
 
 	Assert(segpage % SLRU_PAGES_PER_SEGMENT == 0);
 
-	return (ctl->PagePrecedes(segpage, cutoffPage) &&
-			ctl->PagePrecedes(seg_last_page, cutoffPage));
+	return (ctl->options.PagePrecedes(segpage, cutoffPage) &&
+			ctl->options.PagePrecedes(seg_last_page, cutoffPage));
 }
 
 #ifdef USE_ASSERT_CHECKING
 static void
-SlruPagePrecedesTestOffset(SlruCtl ctl, int per_page, uint32 offset)
+SlruPagePrecedesTestOffset(SlruDesc *ctl, int per_page, uint32 offset)
 {
 	TransactionId lhs,
 				rhs;
@@ -1654,6 +1671,9 @@ SlruPagePrecedesTestOffset(SlruCtl ctl, int per_page, uint32 offset)
 	TransactionId newestXact,
 				oldestXact;
 
+	/* This must be called after the Slru has been initialized */
+	Assert(ctl->options.PagePrecedes);
+
 	/*
 	 * Compare an XID pair having undefined order (see RFC 1982), a pair at
 	 * "opposite ends" of the XID space.  TransactionIdPrecedes() treats each
@@ -1670,19 +1690,19 @@ SlruPagePrecedesTestOffset(SlruCtl ctl, int per_page, uint32 offset)
 	Assert(!TransactionIdPrecedes(rhs, lhs + 1));
 	Assert(!TransactionIdFollowsOrEquals(lhs, rhs));
 	Assert(!TransactionIdFollowsOrEquals(rhs, lhs));
-	Assert(!ctl->PagePrecedes(lhs / per_page, lhs / per_page));
-	Assert(!ctl->PagePrecedes(lhs / per_page, rhs / per_page));
-	Assert(!ctl->PagePrecedes(rhs / per_page, lhs / per_page));
-	Assert(!ctl->PagePrecedes((lhs - per_page) / per_page, rhs / per_page));
-	Assert(ctl->PagePrecedes(rhs / per_page, (lhs - 3 * per_page) / per_page));
-	Assert(ctl->PagePrecedes(rhs / per_page, (lhs - 2 * per_page) / per_page));
-	Assert(ctl->PagePrecedes(rhs / per_page, (lhs - 1 * per_page) / per_page)
+	Assert(!ctl->options.PagePrecedes(lhs / per_page, lhs / per_page));
+	Assert(!ctl->options.PagePrecedes(lhs / per_page, rhs / per_page));
+	Assert(!ctl->options.PagePrecedes(rhs / per_page, lhs / per_page));
+	Assert(!ctl->options.PagePrecedes((lhs - per_page) / per_page, rhs / per_page));
+	Assert(ctl->options.PagePrecedes(rhs / per_page, (lhs - 3 * per_page) / per_page));
+	Assert(ctl->options.PagePrecedes(rhs / per_page, (lhs - 2 * per_page) / per_page));
+	Assert(ctl->options.PagePrecedes(rhs / per_page, (lhs - 1 * per_page) / per_page)
 		   || (1U << 31) % per_page != 0);	/* See CommitTsPagePrecedes() */
-	Assert(ctl->PagePrecedes((lhs + 1 * per_page) / per_page, rhs / per_page)
+	Assert(ctl->options.PagePrecedes((lhs + 1 * per_page) / per_page, rhs / per_page)
 		   || (1U << 31) % per_page != 0);
-	Assert(ctl->PagePrecedes((lhs + 2 * per_page) / per_page, rhs / per_page));
-	Assert(ctl->PagePrecedes((lhs + 3 * per_page) / per_page, rhs / per_page));
-	Assert(!ctl->PagePrecedes(rhs / per_page, (lhs + per_page) / per_page));
+	Assert(ctl->options.PagePrecedes((lhs + 2 * per_page) / per_page, rhs / per_page));
+	Assert(ctl->options.PagePrecedes((lhs + 3 * per_page) / per_page, rhs / per_page));
+	Assert(!ctl->options.PagePrecedes(rhs / per_page, (lhs + per_page) / per_page));
 
 	/*
 	 * GetNewTransactionId() has assigned the last XID it can safely use, and
@@ -1727,7 +1747,7 @@ SlruPagePrecedesTestOffset(SlruCtl ctl, int per_page, uint32 offset)
  * do not apply to them.)
  */
 void
-SlruPagePrecedesUnitTests(SlruCtl ctl, int per_page)
+SlruPagePrecedesUnitTests(SlruDesc *ctl, int per_page)
 {
 	/* Test first, middle and last entries of a page. */
 	SlruPagePrecedesTestOffset(ctl, per_page, 0);
@@ -1742,7 +1762,7 @@ SlruPagePrecedesUnitTests(SlruCtl ctl, int per_page)
  *		one containing the page passed as "data".
  */
 bool
-SlruScanDirCbReportPresence(SlruCtl ctl, char *filename, int64 segpage,
+SlruScanDirCbReportPresence(SlruDesc *ctl, char *filename, int64 segpage,
 							void *data)
 {
 	int64		cutoffPage = *(int64 *) data;
@@ -1758,7 +1778,7 @@ SlruScanDirCbReportPresence(SlruCtl ctl, char *filename, int64 segpage,
  *		This callback deletes segments prior to the one passed in as "data".
  */
 static bool
-SlruScanDirCbDeleteCutoff(SlruCtl ctl, char *filename, int64 segpage,
+SlruScanDirCbDeleteCutoff(SlruDesc *ctl, char *filename, int64 segpage,
 						  void *data)
 {
 	int64		cutoffPage = *(int64 *) data;
@@ -1774,7 +1794,7 @@ SlruScanDirCbDeleteCutoff(SlruCtl ctl, char *filename, int64 segpage,
  *		This callback deletes all segments.
  */
 bool
-SlruScanDirCbDeleteAll(SlruCtl ctl, char *filename, int64 segpage, void *data)
+SlruScanDirCbDeleteAll(SlruDesc *ctl, char *filename, int64 segpage, void *data)
 {
 	SlruInternalDeleteSegment(ctl, segpage / SLRU_PAGES_PER_SEGMENT);
 
@@ -1788,9 +1808,9 @@ SlruScanDirCbDeleteAll(SlruCtl ctl, char *filename, int64 segpage, void *data)
  * SLRU segment.
  */
 static inline bool
-SlruCorrectSegmentFilenameLength(SlruCtl ctl, size_t len)
+SlruCorrectSegmentFilenameLength(SlruDesc *ctl, size_t len)
 {
-	if (ctl->long_segment_names)
+	if (ctl->options.long_segment_names)
 		return (len == 15);		/* see SlruFileName() */
 	else
 
@@ -1821,7 +1841,7 @@ SlruCorrectSegmentFilenameLength(SlruCtl ctl, size_t len)
  * Note that no locking is applied.
  */
 bool
-SlruScanDirectory(SlruCtl ctl, SlruScanCallback callback, void *data)
+SlruScanDirectory(SlruDesc *ctl, SlruScanCallback callback, void *data)
 {
 	bool		retval = false;
 	DIR		   *cldir;
@@ -1829,8 +1849,8 @@ SlruScanDirectory(SlruCtl ctl, SlruScanCallback callback, void *data)
 	int64		segno;
 	int64		segpage;
 
-	cldir = AllocateDir(ctl->Dir);
-	while ((clde = ReadDir(cldir, ctl->Dir)) != NULL)
+	cldir = AllocateDir(ctl->options.Dir);
+	while ((clde = ReadDir(cldir, ctl->options.Dir)) != NULL)
 	{
 		size_t		len;
 
@@ -1843,7 +1863,7 @@ SlruScanDirectory(SlruCtl ctl, SlruScanCallback callback, void *data)
 			segpage = segno * SLRU_PAGES_PER_SEGMENT;
 
 			elog(DEBUG2, "SlruScanDirectory invoking callback on %s/%s",
-				 ctl->Dir, clde->d_name);
+				 ctl->options.Dir, clde->d_name);
 			retval = callback(ctl, clde->d_name, segpage, data);
 			if (retval)
 				break;
@@ -1861,7 +1881,7 @@ SlruScanDirectory(SlruCtl ctl, SlruScanCallback callback, void *data)
  * performs the fsync.
  */
 int
-SlruSyncFileTag(SlruCtl ctl, const FileTag *ftag, char *path)
+SlruSyncFileTag(SlruDesc *ctl, const FileTag *ftag, char *path)
 {
 	int			fd;
 	int			save_errno;
diff --git a/src/backend/access/transam/subtrans.c b/src/backend/access/transam/subtrans.c
index c6ce71fc703..b79e648b899 100644
--- a/src/backend/access/transam/subtrans.c
+++ b/src/backend/access/transam/subtrans.c
@@ -33,6 +33,7 @@
 #include "access/transam.h"
 #include "miscadmin.h"
 #include "pg_trace.h"
+#include "storage/subsystems.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 
@@ -66,16 +67,22 @@ TransactionIdToPage(TransactionId xid)
 #define TransactionIdToEntry(xid) ((xid) % (TransactionId) SUBTRANS_XACTS_PER_PAGE)
 
 
+static void SUBTRANSShmemRequest(void *arg);
+static void SUBTRANSShmemInit(void *arg);
+static bool SubTransPagePrecedes(int64 page1, int64 page2);
+static int	subtrans_errdetail_for_io_error(const void *opaque_data);
+
+const ShmemCallbacks SUBTRANSShmemCallbacks = {
+	.request_fn = SUBTRANSShmemRequest,
+	.init_fn = SUBTRANSShmemInit,
+};
+
 /*
  * Link to shared-memory data structures for SUBTRANS control
  */
-static SlruCtlData SubTransCtlData;
-
-#define SubTransCtl  (&SubTransCtlData)
+static SlruDesc SubTransSlruDesc;
 
-
-static bool SubTransPagePrecedes(int64 page1, int64 page2);
-static int	subtrans_errdetail_for_io_error(const void *opaque_data);
+#define SubTransCtl  (&SubTransSlruDesc)
 
 
 /*
@@ -207,17 +214,13 @@ SUBTRANSShmemBuffers(void)
 	return Min(Max(16, subtransaction_buffers), SLRU_MAX_ALLOWED_BUFFERS);
 }
 
+
+
 /*
- * Initialization of shared memory for SUBTRANS
+ * Register shared memory for SUBTRANS
  */
-Size
-SUBTRANSShmemSize(void)
-{
-	return SimpleLruShmemSize(SUBTRANSShmemBuffers(), 0);
-}
-
-void
-SUBTRANSShmemInit(void)
+static void
+SUBTRANSShmemRequest(void *arg)
 {
 	/* If auto-tuning is requested, now is the time to do it */
 	if (subtransaction_buffers == 0)
@@ -240,11 +243,25 @@ SUBTRANSShmemInit(void)
 	}
 	Assert(subtransaction_buffers != 0);
 
-	SubTransCtl->PagePrecedes = SubTransPagePrecedes;
-	SubTransCtl->errdetail_for_io_error = subtrans_errdetail_for_io_error;
-	SimpleLruInit(SubTransCtl, "subtransaction", SUBTRANSShmemBuffers(), 0,
-				  "pg_subtrans", LWTRANCHE_SUBTRANS_BUFFER,
-				  LWTRANCHE_SUBTRANS_SLRU, SYNC_HANDLER_NONE, false);
+	SimpleLruRequest(.desc = &SubTransSlruDesc,
+					 .name = "subtransaction",
+					 .Dir = "pg_subtrans",
+					 .long_segment_names = false,
+
+					 .nslots = SUBTRANSShmemBuffers(),
+
+					 .sync_handler = SYNC_HANDLER_NONE,
+					 .PagePrecedes = SubTransPagePrecedes,
+					 .errdetail_for_io_error = subtrans_errdetail_for_io_error,
+
+					 .buffer_tranche_id = LWTRANCHE_SUBTRANS_BUFFER,
+					 .bank_tranche_id = LWTRANCHE_SUBTRANS_SLRU,
+		);
+}
+
+static void
+SUBTRANSShmemInit(void *arg)
+{
 	SlruPagePrecedesUnitTests(SubTransCtl, SUBTRANS_XACTS_PER_PAGE);
 }
 
diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c
index e91a62ff42a..db6a9a6561b 100644
--- a/src/backend/commands/async.c
+++ b/src/backend/commands/async.c
@@ -179,6 +179,7 @@
 #include "storage/latch.h"
 #include "storage/lmgr.h"
 #include "storage/procsignal.h"
+#include "storage/subsystems.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
 #include "utils/dsa.h"
@@ -345,6 +346,15 @@ typedef struct AsyncQueueControl
 
 static AsyncQueueControl *asyncQueueControl;
 
+static void AsyncShmemRequest(void *arg);
+static void AsyncShmemInit(void *arg);
+
+const ShmemCallbacks AsyncShmemCallbacks = {
+	.request_fn = AsyncShmemRequest,
+	.init_fn = AsyncShmemInit,
+};
+
+
 #define QUEUE_HEAD					(asyncQueueControl->head)
 #define QUEUE_TAIL					(asyncQueueControl->tail)
 #define QUEUE_STOP_PAGE				(asyncQueueControl->stopPage)
@@ -359,9 +369,13 @@ static AsyncQueueControl *asyncQueueControl;
 /*
  * The SLRU buffer area through which we access the notification queue
  */
-static SlruCtlData NotifyCtlData;
+static inline bool asyncQueuePagePrecedes(int64 p, int64 q);
+static int	asyncQueueErrdetailForIoError(const void *opaque_data);
+
+static SlruDesc NotifySlruDesc;
 
-#define NotifyCtl					(&NotifyCtlData)
+
+#define NotifyCtl					(&NotifySlruDesc)
 #define QUEUE_PAGESIZE				BLCKSZ
 
 #define QUEUE_FULL_WARN_INTERVAL	5000	/* warn at most once every 5s */
@@ -570,9 +584,7 @@ bool		Trace_notify = false;
 int			max_notify_queue_pages = 1048576;
 
 /* local function prototypes */
-static int	asyncQueueErrdetailForIoError(const void *opaque_data);
 static inline int64 asyncQueuePageDiff(int64 p, int64 q);
-static inline bool asyncQueuePagePrecedes(int64 p, int64 q);
 static inline void GlobalChannelKeyInit(GlobalChannelKey *key, Oid dboid,
 										const char *channel);
 static dshash_hash globalChannelTableHash(const void *key, size_t size,
@@ -780,78 +792,63 @@ initPendingListenActions(void)
 }
 
 /*
- * Report space needed for our shared memory area
+ * Register our shared memory needs
  */
-Size
-AsyncShmemSize(void)
+static void
+AsyncShmemRequest(void *arg)
 {
 	Size		size;
 
-	/* This had better match AsyncShmemInit */
 	size = mul_size(MaxBackends, sizeof(QueueBackendStatus));
 	size = add_size(size, offsetof(AsyncQueueControl, backend));
 
-	size = add_size(size, SimpleLruShmemSize(notify_buffers, 0));
+	ShmemRequestStruct(.name = "Async Queue Control",
+					   .size = size,
+					   .ptr = (void **) &asyncQueueControl,
+		);
 
-	return size;
-}
+	SimpleLruRequest(.desc = &NotifySlruDesc,
+					 .name = "notify",
+					 .Dir = "pg_notify",
 
-/*
- * Initialize our shared memory area
- */
-void
-AsyncShmemInit(void)
-{
-	bool		found;
-	Size		size;
+	/* long segment names are used in order to avoid wraparound */
+					 .long_segment_names = true,
 
-	/*
-	 * Create or attach to the AsyncQueueControl structure.
-	 */
-	size = mul_size(MaxBackends, sizeof(QueueBackendStatus));
-	size = add_size(size, offsetof(AsyncQueueControl, backend));
+					 .nslots = notify_buffers,
 
-	asyncQueueControl = (AsyncQueueControl *)
-		ShmemInitStruct("Async Queue Control", size, &found);
+					 .sync_handler = SYNC_HANDLER_NONE,
+					 .PagePrecedes = asyncQueuePagePrecedes,
+					 .errdetail_for_io_error = asyncQueueErrdetailForIoError,
 
-	if (!found)
+					 .buffer_tranche_id = LWTRANCHE_NOTIFY_BUFFER,
+					 .bank_tranche_id = LWTRANCHE_NOTIFY_SLRU,
+		);
+}
+
+static void
+AsyncShmemInit(void *arg)
+{
+	SET_QUEUE_POS(QUEUE_HEAD, 0, 0);
+	SET_QUEUE_POS(QUEUE_TAIL, 0, 0);
+	QUEUE_STOP_PAGE = 0;
+	QUEUE_FIRST_LISTENER = INVALID_PROC_NUMBER;
+	asyncQueueControl->lastQueueFillWarn = 0;
+	asyncQueueControl->globalChannelTableDSA = DSA_HANDLE_INVALID;
+	asyncQueueControl->globalChannelTableDSH = DSHASH_HANDLE_INVALID;
+	for (int i = 0; i < MaxBackends; i++)
 	{
-		/* First time through, so initialize it */
-		SET_QUEUE_POS(QUEUE_HEAD, 0, 0);
-		SET_QUEUE_POS(QUEUE_TAIL, 0, 0);
-		QUEUE_STOP_PAGE = 0;
-		QUEUE_FIRST_LISTENER = INVALID_PROC_NUMBER;
-		asyncQueueControl->lastQueueFillWarn = 0;
-		asyncQueueControl->globalChannelTableDSA = DSA_HANDLE_INVALID;
-		asyncQueueControl->globalChannelTableDSH = DSHASH_HANDLE_INVALID;
-		for (int i = 0; i < MaxBackends; i++)
-		{
-			QUEUE_BACKEND_PID(i) = InvalidPid;
-			QUEUE_BACKEND_DBOID(i) = InvalidOid;
-			QUEUE_NEXT_LISTENER(i) = INVALID_PROC_NUMBER;
-			SET_QUEUE_POS(QUEUE_BACKEND_POS(i), 0, 0);
-			QUEUE_BACKEND_WAKEUP_PENDING(i) = false;
-			QUEUE_BACKEND_IS_ADVANCING(i) = false;
-		}
+		QUEUE_BACKEND_PID(i) = InvalidPid;
+		QUEUE_BACKEND_DBOID(i) = InvalidOid;
+		QUEUE_NEXT_LISTENER(i) = INVALID_PROC_NUMBER;
+		SET_QUEUE_POS(QUEUE_BACKEND_POS(i), 0, 0);
+		QUEUE_BACKEND_WAKEUP_PENDING(i) = false;
+		QUEUE_BACKEND_IS_ADVANCING(i) = false;
 	}
 
 	/*
-	 * Set up SLRU management of the pg_notify data. Note that long segment
-	 * names are used in order to avoid wraparound.
+	 * During start or reboot, clean out the pg_notify directory.
 	 */
-	NotifyCtl->PagePrecedes = asyncQueuePagePrecedes;
-	NotifyCtl->errdetail_for_io_error = asyncQueueErrdetailForIoError;
-	SimpleLruInit(NotifyCtl, "notify", notify_buffers, 0,
-				  "pg_notify", LWTRANCHE_NOTIFY_BUFFER, LWTRANCHE_NOTIFY_SLRU,
-				  SYNC_HANDLER_NONE, true);
-
-	if (!found)
-	{
-		/*
-		 * During start or reboot, clean out the pg_notify directory.
-		 */
-		(void) SlruScanDirectory(NotifyCtl, SlruScanDirCbDeleteAll, NULL);
-	}
+	(void) SlruScanDirectory(NotifyCtl, SlruScanDirCbDeleteAll, NULL);
 }
 
 
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 4f707158303..7a8c69de802 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -101,16 +101,11 @@ CalculateShmemSize(void)
 	/* legacy subsystems */
 	size = add_size(size, BufferManagerShmemSize());
 	size = add_size(size, LockManagerShmemSize());
-	size = add_size(size, PredicateLockShmemSize());
 	size = add_size(size, XLogPrefetchShmemSize());
 	size = add_size(size, XLOGShmemSize());
 	size = add_size(size, XLogRecoveryShmemSize());
-	size = add_size(size, CLOGShmemSize());
-	size = add_size(size, CommitTsShmemSize());
-	size = add_size(size, SUBTRANSShmemSize());
 	size = add_size(size, TwoPhaseShmemSize());
 	size = add_size(size, BackgroundWorkerShmemSize());
-	size = add_size(size, MultiXactShmemSize());
 	size = add_size(size, BackendStatusShmemSize());
 	size = add_size(size, CheckpointerShmemSize());
 	size = add_size(size, AutoVacuumShmemSize());
@@ -123,7 +118,6 @@ CalculateShmemSize(void)
 	size = add_size(size, ApplyLauncherShmemSize());
 	size = add_size(size, BTreeShmemSize());
 	size = add_size(size, SyncScanShmemSize());
-	size = add_size(size, AsyncShmemSize());
 	size = add_size(size, StatsShmemSize());
 	size = add_size(size, WaitEventCustomShmemSize());
 	size = add_size(size, InjectionPointShmemSize());
@@ -270,10 +264,6 @@ CreateOrAttachShmemStructs(void)
 	XLOGShmemInit();
 	XLogPrefetchShmemInit();
 	XLogRecoveryShmemInit();
-	CLOGShmemInit();
-	CommitTsShmemInit();
-	SUBTRANSShmemInit();
-	MultiXactShmemInit();
 	BufferManagerShmemInit();
 
 	/*
@@ -281,11 +271,6 @@ CreateOrAttachShmemStructs(void)
 	 */
 	LockManagerShmemInit();
 
-	/*
-	 * Set up predicate lock manager
-	 */
-	PredicateLockShmemInit();
-
 	/*
 	 * Set up process table
 	 */
@@ -313,7 +298,6 @@ CreateOrAttachShmemStructs(void)
 	 */
 	BTreeShmemInit();
 	SyncScanShmemInit();
-	AsyncShmemInit();
 	StatsShmemInit();
 	WaitEventCustomShmemInit();
 	InjectionPointShmemInit();
diff --git a/src/backend/storage/ipc/shmem.c b/src/backend/storage/ipc/shmem.c
index 29ff6065dda..bc186d6ea17 100644
--- a/src/backend/storage/ipc/shmem.c
+++ b/src/backend/storage/ipc/shmem.c
@@ -134,6 +134,7 @@
 
 #include <unistd.h>
 
+#include "access/slru.h"
 #include "common/int.h"
 #include "fmgr.h"
 #include "funcapi.h"
@@ -549,6 +550,9 @@ InitShmemIndexEntry(ShmemRequest *request)
 		case SHMEM_KIND_HASH:
 			shmem_hash_init(structPtr, request->options);
 			break;
+		case SHMEM_KIND_SLRU:
+			shmem_slru_init(structPtr, request->options);
+			break;
 	}
 }
 
@@ -602,6 +606,9 @@ AttachShmemIndexEntry(ShmemRequest *request, bool missing_ok)
 		case SHMEM_KIND_HASH:
 			shmem_hash_attach(index_entry->location, request->options);
 			break;
+		case SHMEM_KIND_SLRU:
+			shmem_slru_attach(index_entry->location, request->options);
+			break;
 	}
 
 	return true;
diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c
index af03071a71f..9c389b23506 100644
--- a/src/backend/storage/lmgr/predicate.c
+++ b/src/backend/storage/lmgr/predicate.c
@@ -152,10 +152,6 @@
 /*
  * INTERFACE ROUTINES
  *
- * housekeeping for setting up shared memory predicate lock structures
- *		PredicateLockShmemInit(void)
- *		PredicateLockShmemSize(void)
- *
  * predicate lock reporting
  *		GetPredicateLockStatusData(void)
  *		PageIsPredicateLocked(Relation relation, BlockNumber blkno)
@@ -211,6 +207,8 @@
 #include "storage/predicate_internals.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
+#include "storage/shmem.h"
+#include "storage/subsystems.h"
 #include "utils/guc_hooks.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
@@ -322,9 +320,12 @@
 /*
  * The SLRU buffer area through which we access the old xids.
  */
-static SlruCtlData SerialSlruCtlData;
+static bool SerialPagePrecedesLogically(int64 page1, int64 page2);
+static int	serial_errdetail_for_io_error(const void *opaque_data);
 
-#define SerialSlruCtl			(&SerialSlruCtlData)
+static SlruDesc SerialSlruDesc;
+
+#define SerialSlruCtl			(&SerialSlruDesc)
 
 #define SERIAL_PAGESIZE			BLCKSZ
 #define SERIAL_ENTRYSIZE			sizeof(SerCommitSeqNo)
@@ -384,6 +385,17 @@ int			max_predicate_locks_per_page;	/* in guc_tables.c */
  */
 static PredXactList PredXact;
 
+static void PredicateLockShmemRequest(void *arg);
+static void PredicateLockShmemInit(void *arg);
+static void PredicateLockShmemAttach(void *arg);
+
+const ShmemCallbacks PredicateLockShmemCallbacks = {
+	.request_fn = PredicateLockShmemRequest,
+	.init_fn = PredicateLockShmemInit,
+	.attach_fn = PredicateLockShmemAttach,
+};
+
+
 /*
  * This provides a pool of RWConflict data elements to use in conflict lists
  * between transactions.
@@ -431,6 +443,8 @@ static bool MyXactDidWrite = false;
  */
 static SERIALIZABLEXACT *SavedSerializableXact = InvalidSerializableXact;
 
+static int64 max_serializable_xacts;
+
 /* local functions */
 
 static SERIALIZABLEXACT *CreatePredXact(void);
@@ -442,13 +456,12 @@ static void SetPossibleUnsafeConflict(SERIALIZABLEXACT *roXact, SERIALIZABLEXACT
 static void ReleaseRWConflict(RWConflict conflict);
 static void FlagSxactUnsafe(SERIALIZABLEXACT *sxact);
 
-static bool SerialPagePrecedesLogically(int64 page1, int64 page2);
-static int	serial_errdetail_for_io_error(const void *opaque_data);
 static void SerialAdd(TransactionId xid, SerCommitSeqNo minConflictCommitSeqNo);
 static SerCommitSeqNo SerialGetMinConflictCommitSeqNo(TransactionId xid);
 static void SerialSetActiveSerXmin(TransactionId xid);
 
 static uint32 predicatelock_hash(const void *key, Size keysize);
+
 static void SummarizeOldestCommittedSxact(void);
 static Snapshot GetSafeSnapshot(Snapshot origSnapshot);
 static Snapshot GetSerializableTransactionSnapshotInt(Snapshot snapshot,
@@ -1100,71 +1113,53 @@ CheckPointPredicate(void)
 /*------------------------------------------------------------------------*/
 
 /*
- * PredicateLockShmemInit -- Initialize the predicate locking data structures.
- *
- * This is called from CreateSharedMemoryAndSemaphores(), which see for
- * more comments.  In the normal postmaster case, the shared hash tables
- * are created here.  Backends inherit the pointers
- * to the shared tables via fork().  In the EXEC_BACKEND case, each
- * backend re-executes this code to obtain pointers to the already existing
- * shared hash tables.
+ * PredicateLockShmemRequest -- Register the predicate locking data structures.
  */
-void
-PredicateLockShmemInit(void)
+static void
+PredicateLockShmemRequest(void *arg)
 {
-	HASHCTL		info;
 	int64		max_predicate_lock_targets;
 	int64		max_predicate_locks;
-	int64		max_serializable_xacts;
 	int64		max_rw_conflicts;
-	Size		requestSize;
-	bool		found;
-
-#ifndef EXEC_BACKEND
-	Assert(!IsUnderPostmaster);
-#endif
 
 	/*
-	 * Compute size of predicate lock target hashtable. Note these
-	 * calculations must agree with PredicateLockShmemSize!
+	 * Hash tables and other structs are set up by ShmemInitRegistered() /
+	 * ShmemAttachRegistered() via registered descriptors in
+	 * PredicateLockShmemRegister().  Here we do the remaining initialization
+	 * that can't be done in a callback.
 	 */
 	max_predicate_lock_targets = NPREDICATELOCKTARGETENTS();
 
 	/*
-	 * Allocate hash table for PREDICATELOCKTARGET structs.  This stores
+	 * Register hash table for PREDICATELOCKTARGET structs.  This stores
 	 * per-predicate-lock-target information.
 	 */
-	info.keysize = sizeof(PREDICATELOCKTARGETTAG);
-	info.entrysize = sizeof(PREDICATELOCKTARGET);
-	info.num_partitions = NUM_PREDICATELOCK_PARTITIONS;
-
-	PredicateLockTargetHash = ShmemInitHash("PREDICATELOCKTARGET hash",
-											max_predicate_lock_targets,
-											&info,
-											HASH_ELEM | HASH_BLOBS |
-											HASH_PARTITION | HASH_FIXED_SIZE);
-
-	/* Pre-calculate the hash and partition lock of the scratch entry */
-	ScratchTargetTagHash = PredicateLockTargetTagHashCode(&ScratchTargetTag);
-	ScratchPartitionLock = PredicateLockHashPartitionLock(ScratchTargetTagHash);
+	ShmemRequestHash(.name = "PREDICATELOCKTARGET hash",
+					 .nelems = max_predicate_lock_targets,
+					 .ptr = &PredicateLockTargetHash,
+					 .hash_info.keysize = sizeof(PREDICATELOCKTARGETTAG),
+					 .hash_info.entrysize = sizeof(PREDICATELOCKTARGET),
+					 .hash_info.num_partitions = NUM_PREDICATELOCK_PARTITIONS,
+					 .hash_flags = HASH_ELEM | HASH_BLOBS | HASH_PARTITION | HASH_FIXED_SIZE,
+		);
 
 	/*
 	 * Allocate hash table for PREDICATELOCK structs.  This stores per
 	 * xact-lock-of-a-target information.
 	 */
-	info.keysize = sizeof(PREDICATELOCKTAG);
-	info.entrysize = sizeof(PREDICATELOCK);
-	info.hash = predicatelock_hash;
-	info.num_partitions = NUM_PREDICATELOCK_PARTITIONS;
 
 	/* Assume an average of 2 xacts per target */
 	max_predicate_locks = max_predicate_lock_targets * 2;
 
-	PredicateLockHash = ShmemInitHash("PREDICATELOCK hash",
-									  max_predicate_locks,
-									  &info,
-									  HASH_ELEM | HASH_FUNCTION |
-									  HASH_PARTITION | HASH_FIXED_SIZE);
+	ShmemRequestHash(.name = "PREDICATELOCK hash",
+					 .nelems = max_predicate_locks,
+					 .ptr = &PredicateLockHash,
+					 .hash_info.keysize = sizeof(PREDICATELOCKTAG),
+					 .hash_info.entrysize = sizeof(PREDICATELOCK),
+					 .hash_info.hash = predicatelock_hash,
+					 .hash_info.num_partitions = NUM_PREDICATELOCK_PARTITIONS,
+					 .hash_flags = HASH_ELEM | HASH_FUNCTION | HASH_PARTITION | HASH_FIXED_SIZE,
+		);
 
 	/*
 	 * Compute size for serializable transaction hashtable. Note these
@@ -1177,29 +1172,27 @@ PredicateLockShmemInit(void)
 	max_serializable_xacts = (MaxBackends + max_prepared_xacts) * 10;
 
 	/*
-	 * Allocate a list to hold information on transactions participating in
+	 * Register a list to hold information on transactions participating in
 	 * predicate locking.
 	 */
-	requestSize = add_size(PredXactListDataSize,
-						   (mul_size((Size) max_serializable_xacts,
-									 sizeof(SERIALIZABLEXACT))));
-	PredXact = ShmemInitStruct("PredXactList",
-							   requestSize,
-							   &found);
-	Assert(found == IsUnderPostmaster);
+	ShmemRequestStruct(.name = "PredXactList",
+					   .size = add_size(PredXactListDataSize,
+										(mul_size((Size) max_serializable_xacts,
+												  sizeof(SERIALIZABLEXACT)))),
+					   .ptr = (void **) &PredXact,
+		);
 
 	/*
-	 * Allocate hash table for SERIALIZABLEXID structs.  This stores per-xid
+	 * Register hash table for SERIALIZABLEXID structs.  This stores per-xid
 	 * information for serializable transactions which have accessed data.
 	 */
-	info.keysize = sizeof(SERIALIZABLEXIDTAG);
-	info.entrysize = sizeof(SERIALIZABLEXID);
-
-	SerializableXidHash = ShmemInitHash("SERIALIZABLEXID hash",
-										max_serializable_xacts,
-										&info,
-										HASH_ELEM | HASH_BLOBS |
-										HASH_FIXED_SIZE);
+	ShmemRequestHash(.name = "SERIALIZABLEXID hash",
+					 .nelems = max_serializable_xacts,
+					 .ptr = &SerializableXidHash,
+					 .hash_info.keysize = sizeof(SERIALIZABLEXIDTAG),
+					 .hash_info.entrysize = sizeof(SERIALIZABLEXID),
+					 .hash_flags = HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE,
+		);
 
 	/*
 	 * Allocate space for tracking rw-conflicts in lists attached to the
@@ -1214,58 +1207,50 @@ PredicateLockShmemInit(void)
 	 */
 	max_rw_conflicts = max_serializable_xacts * 5;
 
-	requestSize = RWConflictPoolHeaderDataSize +
-		mul_size((Size) max_rw_conflicts,
-				 RWConflictDataSize);
+	ShmemRequestStruct(.name = "RWConflictPool",
+					   .size = RWConflictPoolHeaderDataSize + mul_size((Size) max_rw_conflicts,
+																	   RWConflictDataSize),
+					   .ptr = (void **) &RWConflictPool,
+		);
 
-	RWConflictPool = ShmemInitStruct("RWConflictPool",
-									 requestSize,
-									 &found);
-	Assert(found == IsUnderPostmaster);
-
-	/*
-	 * Create or attach to the header for the list of finished serializable
-	 * transactions.
-	 */
-	FinishedSerializableTransactions = (dlist_head *)
-		ShmemInitStruct("FinishedSerializableTransactions",
-						sizeof(dlist_head),
-						&found);
-	Assert(found == IsUnderPostmaster);
+	ShmemRequestStruct(.name = "FinishedSerializableTransactions",
+					   .size = sizeof(dlist_head),
+					   .ptr = (void **) &FinishedSerializableTransactions,
+		);
 
 	/*
 	 * Initialize the SLRU storage for old committed serializable
 	 * transactions.
 	 */
-	SerialSlruCtl->PagePrecedes = SerialPagePrecedesLogically;
-	SerialSlruCtl->errdetail_for_io_error = serial_errdetail_for_io_error;
-	SimpleLruInit(SerialSlruCtl, "serializable",
-				  serializable_buffers, 0, "pg_serial",
-				  LWTRANCHE_SERIAL_BUFFER, LWTRANCHE_SERIAL_SLRU,
-				  SYNC_HANDLER_NONE, false);
+	SimpleLruRequest(.desc = &SerialSlruDesc,
+					 .name = "serializable",
+					 .Dir = "pg_serial",
+					 .long_segment_names = false,
+
+					 .nslots = serializable_buffers,
+
+					 .sync_handler = SYNC_HANDLER_NONE,
+					 .PagePrecedes = SerialPagePrecedesLogically,
+					 .errdetail_for_io_error = serial_errdetail_for_io_error,
+
+					 .buffer_tranche_id = LWTRANCHE_SERIAL_BUFFER,
+					 .bank_tranche_id = LWTRANCHE_SERIAL_SLRU,
+		);
 #ifdef USE_ASSERT_CHECKING
 	SerialPagePrecedesLogicallyUnitTests();
 #endif
-	SlruPagePrecedesUnitTests(SerialSlruCtl, SERIAL_ENTRIESPERPAGE);
 
-	/*
-	 * Create or attach to the SerialControl structure.
-	 */
-	serialControl = (SerialControl)
-		ShmemInitStruct("SerialControlData", sizeof(SerialControlData), &found);
-	Assert(found == IsUnderPostmaster);
+	ShmemRequestStruct(.name = "SerialControlData",
+					   .size = sizeof(SerialControlData),
+					   .ptr = (void **) &serialControl,
+		);
+}
 
-	/*
-	 * If we just attached to existing shared memory (EXEC_BACKEND), we're all
-	 * done.  Otherwise, during postmaster startup proceed to initialize the
-	 * shared memory.
-	 */
-	if (IsUnderPostmaster)
-	{
-		/* This never changes, so let's keep a local copy. */
-		OldCommittedSxact = PredXact->OldCommittedSxact;
-		return;
-	}
+static void
+PredicateLockShmemInit(void *arg)
+{
+	int			max_rw_conflicts;
+	bool		found;
 
 	/*
 	 * Reserve a dummy entry in the hash table; we use it to make sure there's
@@ -1277,7 +1262,6 @@ PredicateLockShmemInit(void)
 					   HASH_ENTER, &found);
 	Assert(!found);
 
-	/* Initialize PredXact list */
 	dlist_init(&PredXact->availableList);
 	dlist_init(&PredXact->activeList);
 	PredXact->SxactGlobalXmin = InvalidTransactionId;
@@ -1319,6 +1303,9 @@ PredicateLockShmemInit(void)
 	dlist_init(&RWConflictPool->availableList);
 	RWConflictPool->element = (RWConflict) ((char *) RWConflictPool +
 											RWConflictPoolHeaderDataSize);
+
+	max_rw_conflicts = max_serializable_xacts * 5;
+
 	/* Add all elements to available list, clean. */
 	for (int i = 0; i < max_rw_conflicts; i++)
 	{
@@ -1335,57 +1322,28 @@ PredicateLockShmemInit(void)
 	serialControl->headXid = InvalidTransactionId;
 	serialControl->tailXid = InvalidTransactionId;
 	LWLockRelease(SerialControlLock);
-}
-
-/*
- * Estimate shared-memory space used for predicate lock table
- */
-Size
-PredicateLockShmemSize(void)
-{
-	Size		size = 0;
-	int64		max_predicate_lock_targets;
-	int64		max_predicate_locks;
-	int64		max_serializable_xacts;
-	int64		max_rw_conflicts;
-
-	/* predicate lock target hash table */
-	max_predicate_lock_targets = NPREDICATELOCKTARGETENTS();
-	size = add_size(size, hash_estimate_size(max_predicate_lock_targets,
-											 sizeof(PREDICATELOCKTARGET)));
-
-	/* predicate lock hash table */
-	max_predicate_locks = max_predicate_lock_targets * 2;
-	size = add_size(size, hash_estimate_size(max_predicate_locks,
-											 sizeof(PREDICATELOCK)));
-
-	/* transaction list */
-	max_serializable_xacts = (MaxBackends + max_prepared_xacts) * 10;
-	size = add_size(size, PredXactListDataSize);
-	size = add_size(size, mul_size((Size) max_serializable_xacts,
-								   sizeof(SERIALIZABLEXACT)));
 
-	/* transaction xid table */
-	size = add_size(size, hash_estimate_size(max_serializable_xacts,
-											 sizeof(SERIALIZABLEXID)));
+	/* This never changes, so let's keep a local copy. */
+	OldCommittedSxact = PredXact->OldCommittedSxact;
 
-	/* rw-conflict pool */
-	max_rw_conflicts = max_serializable_xacts * 5;
-	size = add_size(size, RWConflictPoolHeaderDataSize);
-	size = add_size(size, mul_size((Size) max_rw_conflicts,
-								   RWConflictDataSize));
+	/* Pre-calculate the hash and partition lock of the scratch entry */
+	ScratchTargetTagHash = PredicateLockTargetTagHashCode(&ScratchTargetTag);
+	ScratchPartitionLock = PredicateLockHashPartitionLock(ScratchTargetTagHash);
 
-	/* Head for list of finished serializable transactions. */
-	size = add_size(size, sizeof(dlist_head));
+	SlruPagePrecedesUnitTests(SerialSlruCtl, SERIAL_ENTRIESPERPAGE);
+}
 
-	/* Shared memory structures for SLRU tracking of old committed xids. */
-	size = add_size(size, sizeof(SerialControlData));
-	size = add_size(size, SimpleLruShmemSize(serializable_buffers, 0));
+static void
+PredicateLockShmemAttach(void *arg)
+{
+	/* This never changes, so let's keep a local copy. */
+	OldCommittedSxact = PredXact->OldCommittedSxact;
 
-	return size;
+	/* Pre-calculate the hash and partition lock of the scratch entry */
+	ScratchTargetTagHash = PredicateLockTargetTagHashCode(&ScratchTargetTag);
+	ScratchPartitionLock = PredicateLockHashPartitionLock(ScratchTargetTagHash);
 }
 
-
 /*
  * Compute the hash code associated with a PREDICATELOCKTAG.
  *
diff --git a/src/backend/utils/activity/pgstat_slru.c b/src/backend/utils/activity/pgstat_slru.c
index 2190f388eae..f4dfe8697d7 100644
--- a/src/backend/utils/activity/pgstat_slru.c
+++ b/src/backend/utils/activity/pgstat_slru.c
@@ -119,6 +119,7 @@ pgstat_get_slru_index(const char *name)
 {
 	int			i;
 
+	Assert(name);
 	for (i = 0; i < SLRU_NUM_ELEMENTS; i++)
 	{
 		if (strcmp(slru_names[i], name) == 0)
diff --git a/src/include/access/clog.h b/src/include/access/clog.h
index a1cfed5f43c..7894998c763 100644
--- a/src/include/access/clog.h
+++ b/src/include/access/clog.h
@@ -40,8 +40,6 @@ extern void TransactionIdSetTreeStatus(TransactionId xid, int nsubxids,
 									   TransactionId *subxids, XidStatus status, XLogRecPtr lsn);
 extern XidStatus TransactionIdGetStatus(TransactionId xid, XLogRecPtr *lsn);
 
-extern Size CLOGShmemSize(void);
-extern void CLOGShmemInit(void);
 extern void BootStrapCLOG(void);
 extern void StartupCLOG(void);
 extern void TrimCLOG(void);
diff --git a/src/include/access/commit_ts.h b/src/include/access/commit_ts.h
index 49ee21cd5d2..825ccda90ed 100644
--- a/src/include/access/commit_ts.h
+++ b/src/include/access/commit_ts.h
@@ -27,8 +27,6 @@ extern bool TransactionIdGetCommitTsData(TransactionId xid,
 extern TransactionId GetLatestCommitTsData(TimestampTz *ts,
 										   ReplOriginId *nodeid);
 
-extern Size CommitTsShmemSize(void);
-extern void CommitTsShmemInit(void);
 extern void BootStrapCommitTs(void);
 extern void StartupCommitTs(void);
 extern void CommitTsParameterChange(bool newvalue, bool oldvalue);
diff --git a/src/include/access/multixact.h b/src/include/access/multixact.h
index 2ae8b571dcc..6be5299ab68 100644
--- a/src/include/access/multixact.h
+++ b/src/include/access/multixact.h
@@ -121,8 +121,6 @@ extern void AtEOXact_MultiXact(void);
 extern void AtPrepare_MultiXact(void);
 extern void PostPrepare_MultiXact(FullTransactionId fxid);
 
-extern Size MultiXactShmemSize(void);
-extern void MultiXactShmemInit(void);
 extern void BootStrapMultiXact(void);
 extern void StartupMultiXact(void);
 extern void TrimMultiXact(void);
diff --git a/src/include/access/slru.h b/src/include/access/slru.h
index f966d0d9fe7..36a7514d7a0 100644
--- a/src/include/access/slru.h
+++ b/src/include/access/slru.h
@@ -16,6 +16,7 @@
 #include "access/transam.h"
 #include "access/xlogdefs.h"
 #include "storage/lwlock.h"
+#include "storage/shmem.h"
 #include "storage/sync.h"
 
 /*
@@ -106,23 +107,28 @@ typedef struct SlruSharedData
 
 typedef SlruSharedData *SlruShared;
 
-/*
- * SlruCtlData is an unshared structure that points to the active information
- * in shared memory.
- */
-typedef struct SlruCtlData
+typedef struct SlruDesc SlruDesc;
+
+typedef struct SlruOpts
 {
-	SlruShared	shared;
+	ShmemStructOpts base;
 
-	/* Number of banks in this SLRU. */
-	uint16		nbanks;
+	/*
+	 * name of SLRU.  (This is user-visible, pick with care!)
+	 */
+	const char *name;
 
 	/*
-	 * If true, use long segment file names.  Otherwise, use short file names.
-	 *
-	 * For details about the file name format, see SlruFileName().
+	 * Pointer to a backend-private handle for the SLRU.  It is initialized in
+	 * when the SLRU is initialized or attached to.
 	 */
-	bool		long_segment_names;
+	SlruDesc   *desc;
+
+	/* number of page slots to use. */
+	int			nslots;
+
+	/* number of LSN groups per page (set to zero if not relevant). */
+	int			nlsns;
 
 	/*
 	 * Which sync handler function to use when handing sync requests over to
@@ -130,6 +136,19 @@ typedef struct SlruCtlData
 	 */
 	SyncRequestHandler sync_handler;
 
+	/*
+	 * PGDATA-relative subdirectory that will contain the files.
+	 */
+	const char *Dir;
+
+	/*
+	 * If true, use long segment file names.  Otherwise, use short file names.
+	 *
+	 * For details about the file name format, see SlruFileName().
+	 */
+	bool		long_segment_names;
+
+
 	/*
 	 * Decide whether a page is "older" for truncation and as a hint for
 	 * evicting pages in LRU order.  Return true if every entry of the first
@@ -153,13 +172,26 @@ typedef struct SlruCtlData
 	int			(*errdetail_for_io_error) (const void *opaque_data);
 
 	/*
-	 * Dir is set during SimpleLruInit and does not change thereafter. Since
-	 * it's always the same, it doesn't need to be in shared memory.
+	 * Tranche IDs to use for the SLRU's per-buffer and per-bank LWLocks.  If
+	 * these are left as zeros, new tranches will be assigned dynamically.
 	 */
-	char		Dir[64];
-} SlruCtlData;
+	int			buffer_tranche_id;
+	int			bank_tranche_id;
+} SlruOpts;
 
-typedef SlruCtlData *SlruCtl;
+/*
+ * SlruDesc is an unshared structure that points to the active information
+ * in shared memory.
+ */
+typedef struct SlruDesc
+{
+	SlruOpts	options;
+
+	SlruShared	shared;
+
+	/* Number of banks in this SLRU. */
+	uint16		nbanks;
+} SlruDesc;
 
 /*
  * Get the SLRU bank lock for given SlruCtl and the pageno.
@@ -168,48 +200,52 @@ typedef SlruCtlData *SlruCtl;
  * respective bank.
  */
 static inline LWLock *
-SimpleLruGetBankLock(SlruCtl ctl, int64 pageno)
+SimpleLruGetBankLock(SlruDesc *ctl, int64 pageno)
 {
 	int			bankno;
 
+	Assert(ctl->nbanks != 0);
 	bankno = pageno % ctl->nbanks;
 	return &(ctl->shared->bank_locks[bankno].lock);
 }
 
-extern Size SimpleLruShmemSize(int nslots, int nlsns);
+extern void SimpleLruRequestWithOpts(const SlruOpts *options);
+
+#define SimpleLruRequest(...)  \
+	SimpleLruRequestWithOpts(&(SlruOpts){__VA_ARGS__})
+
 extern int	SimpleLruAutotuneBuffers(int divisor, int max);
-extern void SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns,
-						  const char *subdir, int buffer_tranche_id,
-						  int bank_tranche_id, SyncRequestHandler sync_handler,
-						  bool long_segment_names);
-extern int	SimpleLruZeroPage(SlruCtl ctl, int64 pageno);
-extern void SimpleLruZeroAndWritePage(SlruCtl ctl, int64 pageno);
-extern int	SimpleLruReadPage(SlruCtl ctl, int64 pageno, bool write_ok,
+extern int	SimpleLruZeroPage(SlruDesc *ctl, int64 pageno);
+extern void SimpleLruZeroAndWritePage(SlruDesc *ctl, int64 pageno);
+extern int	SimpleLruReadPage(SlruDesc *ctl, int64 pageno, bool write_ok,
 							  const void *opaque_data);
-extern int	SimpleLruReadPage_ReadOnly(SlruCtl ctl, int64 pageno,
+extern int	SimpleLruReadPage_ReadOnly(SlruDesc *ctl, int64 pageno,
 									   const void *opaque_data);
-extern void SimpleLruWritePage(SlruCtl ctl, int slotno);
-extern void SimpleLruWriteAll(SlruCtl ctl, bool allow_redirtied);
+extern void SimpleLruWritePage(SlruDesc *ctl, int slotno);
+extern void SimpleLruWriteAll(SlruDesc *ctl, bool allow_redirtied);
 #ifdef USE_ASSERT_CHECKING
-extern void SlruPagePrecedesUnitTests(SlruCtl ctl, int per_page);
+extern void SlruPagePrecedesUnitTests(SlruDesc *ctl, int per_page);
 #else
 #define SlruPagePrecedesUnitTests(ctl, per_page) do {} while (0)
 #endif
-extern void SimpleLruTruncate(SlruCtl ctl, int64 cutoffPage);
-extern bool SimpleLruDoesPhysicalPageExist(SlruCtl ctl, int64 pageno);
+extern void SimpleLruTruncate(SlruDesc *ctl, int64 cutoffPage);
+extern bool SimpleLruDoesPhysicalPageExist(SlruDesc *ctl, int64 pageno);
 
-typedef bool (*SlruScanCallback) (SlruCtl ctl, char *filename, int64 segpage,
+typedef bool (*SlruScanCallback) (SlruDesc *ctl, char *filename, int64 segpage,
 								  void *data);
-extern bool SlruScanDirectory(SlruCtl ctl, SlruScanCallback callback, void *data);
-extern void SlruDeleteSegment(SlruCtl ctl, int64 segno);
+extern bool SlruScanDirectory(SlruDesc *ctl, SlruScanCallback callback, void *data);
+extern void SlruDeleteSegment(SlruDesc *ctl, int64 segno);
 
-extern int	SlruSyncFileTag(SlruCtl ctl, const FileTag *ftag, char *path);
+extern int	SlruSyncFileTag(SlruDesc *ctl, const FileTag *ftag, char *path);
 
 /* SlruScanDirectory public callbacks */
-extern bool SlruScanDirCbReportPresence(SlruCtl ctl, char *filename,
+extern bool SlruScanDirCbReportPresence(SlruDesc *ctl, char *filename,
 										int64 segpage, void *data);
-extern bool SlruScanDirCbDeleteAll(SlruCtl ctl, char *filename, int64 segpage,
+extern bool SlruScanDirCbDeleteAll(SlruDesc *ctl, char *filename, int64 segpage,
 								   void *data);
 extern bool check_slru_buffers(const char *name, int *newval);
 
+extern void shmem_slru_init(void *location, ShmemStructOpts *options);
+extern void shmem_slru_attach(void *location, ShmemStructOpts *options);
+
 #endif							/* SLRU_H */
diff --git a/src/include/access/subtrans.h b/src/include/access/subtrans.h
index 11b7355dbdf..d986cd9e802 100644
--- a/src/include/access/subtrans.h
+++ b/src/include/access/subtrans.h
@@ -15,8 +15,6 @@ extern void SubTransSetParent(TransactionId xid, TransactionId parent);
 extern TransactionId SubTransGetParent(TransactionId xid);
 extern TransactionId SubTransGetTopmostTransaction(TransactionId xid);
 
-extern Size SUBTRANSShmemSize(void);
-extern void SUBTRANSShmemInit(void);
 extern void BootStrapSUBTRANS(void);
 extern void StartupSUBTRANS(TransactionId oldestActiveXID);
 extern void CheckPointSUBTRANS(void);
diff --git a/src/include/commands/async.h b/src/include/commands/async.h
index 3baae7cb8dc..202e4aa5e74 100644
--- a/src/include/commands/async.h
+++ b/src/include/commands/async.h
@@ -19,9 +19,6 @@ extern PGDLLIMPORT bool Trace_notify;
 extern PGDLLIMPORT int max_notify_queue_pages;
 extern PGDLLIMPORT volatile sig_atomic_t notifyInterruptPending;
 
-extern Size AsyncShmemSize(void);
-extern void AsyncShmemInit(void);
-
 extern void NotifyMyFrontEnd(const char *channel,
 							 const char *payload,
 							 int32 srcPid);
diff --git a/src/include/storage/predicate.h b/src/include/storage/predicate.h
index a5ac55b8f7e..443bffb58fd 100644
--- a/src/include/storage/predicate.h
+++ b/src/include/storage/predicate.h
@@ -41,11 +41,6 @@ typedef void *SerializableXactHandle;
 /*
  * function prototypes
  */
-
-/* housekeeping for shared memory predicate lock structures */
-extern void PredicateLockShmemInit(void);
-extern Size PredicateLockShmemSize(void);
-
 extern void CheckPointPredicate(void);
 
 /* predicate lock reporting */
diff --git a/src/include/storage/shmem_internal.h b/src/include/storage/shmem_internal.h
index fe12bf33439..7b259d33ccf 100644
--- a/src/include/storage/shmem_internal.h
+++ b/src/include/storage/shmem_internal.h
@@ -21,6 +21,7 @@ typedef enum
 {
 	SHMEM_KIND_STRUCT = 0,		/* plain, contiguous area of memory */
 	SHMEM_KIND_HASH,			/* a hash table */
+	SHMEM_KIND_SLRU,			/* SLRU buffers and control structures */
 } ShmemRequestKind;
 
 /* shmem.c */
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index d62c29f1361..c199f18a27a 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -32,6 +32,13 @@ PG_SHMEM_SUBSYSTEM(DSMRegistryShmemCallbacks)
 
 /* xlog, clog, and buffers */
 PG_SHMEM_SUBSYSTEM(VarsupShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(CLOGShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(CommitTsShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(SUBTRANSShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(MultiXactShmemCallbacks)
+
+/* predicate lock manager */
+PG_SHMEM_SUBSYSTEM(PredicateLockShmemCallbacks)
 
 /* process table */
 PG_SHMEM_SUBSYSTEM(ProcGlobalShmemCallbacks)
@@ -43,3 +50,6 @@ PG_SHMEM_SUBSYSTEM(SharedInvalShmemCallbacks)
 /* interprocess signaling mechanisms */
 PG_SHMEM_SUBSYSTEM(PMSignalShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ProcSignalShmemCallbacks)
+
+/* other modules that need some shared memory space */
+PG_SHMEM_SUBSYSTEM(AsyncShmemCallbacks)
diff --git a/src/test/modules/test_slru/test_slru.c b/src/test/modules/test_slru/test_slru.c
index e4bd2af0bf5..40efffdbf62 100644
--- a/src/test/modules/test_slru/test_slru.c
+++ b/src/test/modules/test_slru/test_slru.c
@@ -40,14 +40,22 @@ PG_FUNCTION_INFO_V1(test_slru_delete_all);
 /* Number of SLRU page slots */
 #define NUM_TEST_BUFFERS		16
 
-static SlruCtlData TestSlruCtlData;
-#define TestSlruCtl			(&TestSlruCtlData)
+static void test_slru_shmem_request(void *arg);
+static bool test_slru_page_precedes_logically(int64 page1, int64 page2);
+static int	test_slru_errdetail_for_io_error(const void *opaque_data);
 
-static shmem_request_hook_type prev_shmem_request_hook = NULL;
-static shmem_startup_hook_type prev_shmem_startup_hook = NULL;
+static const char *TestSlruDir = "pg_test_slru";
+
+static SlruDesc TestSlruDesc;
+
+static const ShmemCallbacks test_slru_shmem_callbacks = {
+	.request_fn = test_slru_shmem_request
+};
+
+#define TestSlruCtl			(&TestSlruDesc)
 
 static bool
-test_slru_scan_cb(SlruCtl ctl, char *filename, int64 segpage, void *data)
+test_slru_scan_cb(SlruDesc *ctl, char *filename, int64 segpage, void *data)
 {
 	elog(NOTICE, "Calling test_slru_scan_cb()");
 	return SlruScanDirCbDeleteAll(ctl, filename, segpage, data);
@@ -190,20 +198,6 @@ test_slru_delete_all(PG_FUNCTION_ARGS)
 	PG_RETURN_VOID();
 }
 
-/*
- * Module load callbacks and initialization.
- */
-
-static void
-test_slru_shmem_request(void)
-{
-	if (prev_shmem_request_hook)
-		prev_shmem_request_hook();
-
-	/* reserve shared memory for the test SLRU */
-	RequestAddinShmemSpace(SimpleLruShmemSize(NUM_TEST_BUFFERS, 0));
-}
-
 static bool
 test_slru_page_precedes_logically(int64 page1, int64 page2)
 {
@@ -218,60 +212,46 @@ test_slru_errdetail_for_io_error(const void *opaque_data)
 	return errdetail("Could not access test_slru entry %u.", xid);
 }
 
-static void
-test_slru_shmem_startup(void)
+void
+_PG_init(void)
 {
-	/*
-	 * Short segments names are well tested elsewhere so in this test we are
-	 * focusing on long names.
-	 */
-	const bool	long_segment_names = true;
-	const char	slru_dir_name[] = "pg_test_slru";
-	int			test_tranche_id = -1;
-	int			test_buffer_tranche_id = -1;
-
-	if (prev_shmem_startup_hook)
-		prev_shmem_startup_hook();
+	if (!process_shared_preload_libraries_in_progress)
+		ereport(ERROR,
+				(errmsg("cannot load \"%s\" after startup", "test_slru"),
+				 errdetail("\"%s\" must be loaded with \"shared_preload_libraries\".",
+						   "test_slru")));
 
 	/*
 	 * Create the SLRU directory if it does not exist yet, from the root of
 	 * the data directory.
 	 */
-	(void) MakePGDirectory(slru_dir_name);
+	(void) MakePGDirectory(TestSlruDir);
 
-	/*
-	 * Initialize the SLRU facility.  In EXEC_BACKEND builds, the
-	 * shmem_startup_hook is called in the postmaster and in each backend, but
-	 * we only need to generate the LWLock tranches once.  Note that these
-	 * tranche ID variables are not used by SimpleLruInit() when
-	 * IsUnderPostmaster is true.
-	 */
-	if (!IsUnderPostmaster)
-	{
-		test_tranche_id = LWLockNewTrancheId("test_slru_tranche");
-		test_buffer_tranche_id = LWLockNewTrancheId("test_buffer_tranche");
-	}
-
-	TestSlruCtl->PagePrecedes = test_slru_page_precedes_logically;
-	TestSlruCtl->errdetail_for_io_error = test_slru_errdetail_for_io_error;
-	SimpleLruInit(TestSlruCtl, "TestSLRU",
-				  NUM_TEST_BUFFERS, 0, slru_dir_name,
-				  test_buffer_tranche_id, test_tranche_id, SYNC_HANDLER_NONE,
-				  long_segment_names);
+	RegisterShmemCallbacks(&test_slru_shmem_callbacks);
 }
 
-void
-_PG_init(void)
+static void
+test_slru_shmem_request(void *arg)
 {
-	if (!process_shared_preload_libraries_in_progress)
-		ereport(ERROR,
-				(errmsg("cannot load \"%s\" after startup", "test_slru"),
-				 errdetail("\"%s\" must be loaded with \"shared_preload_libraries\".",
-						   "test_slru")));
+	SimpleLruRequest(.desc = &TestSlruDesc,
+					 .name = "TestSLRU",
+					 .Dir = TestSlruDir,
+
+	/*
+	 * Short segments names are well tested elsewhere so in this test we are
+	 * focusing on long names.
+	 */
+					 .long_segment_names = true,
+
+					 .nslots = NUM_TEST_BUFFERS,
+					 .nlsns = 0,
 
-	prev_shmem_request_hook = shmem_request_hook;
-	shmem_request_hook = test_slru_shmem_request;
+					 .sync_handler = SYNC_HANDLER_NONE,
+					 .PagePrecedes = test_slru_page_precedes_logically,
+					 .errdetail_for_io_error = test_slru_errdetail_for_io_error,
 
-	prev_shmem_startup_hook = shmem_startup_hook;
-	shmem_startup_hook = test_slru_shmem_startup;
+	/* let slru.c assign these */
+					 .buffer_tranche_id = 0,
+					 .bank_tranche_id = 0,
+		);
 }
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 63c0b3a9465..3c35097361d 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2901,9 +2901,9 @@ SlotInvalidationCauseMap
 SlotNumber
 SlotSyncCtxStruct
 SlotSyncSkipReason
-SlruCtl
-SlruCtlData
+SlruDesc
 SlruErrorCause
+SlruOpts
 SlruPageStatus
 SlruScanCallback
 SlruSegState
-- 
2.34.1



  [text/x-patch] v20260405-0011-Convert-AIO-to-the-new-interface.patch (14.6K, 12-v20260405-0011-Convert-AIO-to-the-new-interface.patch)
  download | inline diff:
From a621b2a0acb2397294223d2a96ea52337d884832 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Sat, 21 Mar 2026 12:43:16 +0200
Subject: [PATCH v20260405 11/15] Convert AIO to the new interface

This replaces the "shmem_size" and "shmem_init" callbacks in the IO
methods table with the same ShmemCallback struct that we now use in
other subsystems
---
 src/backend/storage/aio/aio_init.c        | 112 +++++++++++++---------
 src/backend/storage/aio/method_io_uring.c |  39 ++++----
 src/backend/storage/aio/method_worker.c   |  84 +++++++++-------
 src/backend/storage/ipc/ipci.c            |   2 -
 src/include/storage/aio_internal.h        |  16 +---
 src/include/storage/aio_subsys.h          |   4 -
 src/include/storage/subsystemlist.h       |   3 +
 7 files changed, 143 insertions(+), 117 deletions(-)

diff --git a/src/backend/storage/aio/aio_init.c b/src/backend/storage/aio/aio_init.c
index d3c68d8b04c..18bb4235044 100644
--- a/src/backend/storage/aio/aio_init.c
+++ b/src/backend/storage/aio/aio_init.c
@@ -23,16 +23,24 @@
 #include "storage/ipc.h"
 #include "storage/proc.h"
 #include "storage/shmem.h"
+#include "storage/subsystems.h"
 #include "utils/guc.h"
 
 
+static void AioShmemRequest(void *arg);
+static void AioShmemInit(void *arg);
+static void AioShmemAttach(void *arg);
 
-static Size
-AioCtlShmemSize(void)
-{
-	/* pgaio_ctl itself */
-	return sizeof(PgAioCtl);
-}
+const ShmemCallbacks AioShmemCallbacks = {
+	.request_fn = AioShmemRequest,
+	.init_fn = AioShmemInit,
+	.attach_fn = AioShmemAttach,
+};
+
+static PgAioBackend *AioBackendShmemPtr;
+static PgAioHandle *AioHandleShmemPtr;
+static struct iovec *AioHandleIOVShmemPtr;
+static uint64 *AioHandleDataShmemPtr;
 
 static uint32
 AioProcs(void)
@@ -109,12 +117,15 @@ AioChooseMaxConcurrency(void)
 	return Min(max_proportional_pins, 64);
 }
 
-Size
-AioShmemSize(void)
+/*
+ * Register shared memory area for AIO subsystem.
+ */
+static void
+AioShmemRequest(void *arg)
 {
-	Size		sz = 0;
-
 	/*
+	 * Resolve io_max_concurrency if not already done
+	 *
 	 * We prefer to report this value's source as PGC_S_DYNAMIC_DEFAULT.
 	 * However, if the DBA explicitly set io_max_concurrency = -1 in the
 	 * config file, then PGC_S_DYNAMIC_DEFAULT will fail to override that and
@@ -132,48 +143,52 @@ AioShmemSize(void)
 							PGC_S_OVERRIDE);
 	}
 
-	sz = add_size(sz, AioCtlShmemSize());
-	sz = add_size(sz, AioBackendShmemSize());
-	sz = add_size(sz, AioHandleShmemSize());
-	sz = add_size(sz, AioHandleIOVShmemSize());
-	sz = add_size(sz, AioHandleDataShmemSize());
-
-	/* Reserve space for method specific resources. */
-	if (pgaio_method_ops->shmem_size)
-		sz = add_size(sz, pgaio_method_ops->shmem_size());
-
-	return sz;
+	ShmemRequestStruct(.name = "AioCtl",
+					   .size = sizeof(PgAioCtl),
+					   .ptr = (void **) &pgaio_ctl,
+		);
+
+	ShmemRequestStruct(.name = "AioBackend",
+					   .size = AioBackendShmemSize(),
+					   .ptr = (void **) &AioBackendShmemPtr,
+		);
+
+	ShmemRequestStruct(.name = "AioHandle",
+					   .size = AioHandleShmemSize(),
+					   .ptr = (void **) &AioHandleShmemPtr,
+		);
+
+	ShmemRequestStruct(.name = "AioHandleIOV",
+					   .size = AioHandleIOVShmemSize(),
+					   .ptr = (void **) &AioHandleIOVShmemPtr,
+		);
+
+	ShmemRequestStruct(.name = "AioHandleData",
+					   .size = AioHandleDataShmemSize(),
+					   .ptr = (void **) &AioHandleDataShmemPtr,
+		);
+
+	if (pgaio_method_ops->shmem_callbacks.request_fn)
+		pgaio_method_ops->shmem_callbacks.request_fn(pgaio_method_ops->shmem_callbacks.request_fn_arg);
 }
 
-void
-AioShmemInit(void)
+/*
+ * Initialize AIO shared memory during postmaster startup.
+ */
+static void
+AioShmemInit(void *arg)
 {
-	bool		found;
 	uint32		io_handle_off = 0;
 	uint32		iovec_off = 0;
 	uint32		per_backend_iovecs = io_max_concurrency * io_max_combine_limit;
 
-	pgaio_ctl = (PgAioCtl *)
-		ShmemInitStruct("AioCtl", AioCtlShmemSize(), &found);
-
-	if (found)
-		goto out;
-
-	memset(pgaio_ctl, 0, AioCtlShmemSize());
-
 	pgaio_ctl->io_handle_count = AioProcs() * io_max_concurrency;
 	pgaio_ctl->iovec_count = AioProcs() * per_backend_iovecs;
 
-	pgaio_ctl->backend_state = (PgAioBackend *)
-		ShmemInitStruct("AioBackend", AioBackendShmemSize(), &found);
-
-	pgaio_ctl->io_handles = (PgAioHandle *)
-		ShmemInitStruct("AioHandle", AioHandleShmemSize(), &found);
-
-	pgaio_ctl->iovecs = (struct iovec *)
-		ShmemInitStruct("AioHandleIOV", AioHandleIOVShmemSize(), &found);
-	pgaio_ctl->handle_data = (uint64 *)
-		ShmemInitStruct("AioHandleData", AioHandleDataShmemSize(), &found);
+	pgaio_ctl->backend_state = AioBackendShmemPtr;
+	pgaio_ctl->io_handles = AioHandleShmemPtr;
+	pgaio_ctl->iovecs = AioHandleIOVShmemPtr;
+	pgaio_ctl->handle_data = AioHandleDataShmemPtr;
 
 	for (int procno = 0; procno < AioProcs(); procno++)
 	{
@@ -208,10 +223,15 @@ AioShmemInit(void)
 		}
 	}
 
-out:
-	/* Initialize IO method specific resources. */
-	if (pgaio_method_ops->shmem_init)
-		pgaio_method_ops->shmem_init(!found);
+	if (pgaio_method_ops->shmem_callbacks.init_fn)
+		pgaio_method_ops->shmem_callbacks.init_fn(pgaio_method_ops->shmem_callbacks.init_fn_arg);
+}
+
+static void
+AioShmemAttach(void *arg)
+{
+	if (pgaio_method_ops->shmem_callbacks.attach_fn)
+		pgaio_method_ops->shmem_callbacks.attach_fn(pgaio_method_ops->shmem_callbacks.attach_fn_arg);
 }
 
 void
diff --git a/src/backend/storage/aio/method_io_uring.c b/src/backend/storage/aio/method_io_uring.c
index 9f76d2683c0..3295c59ed75 100644
--- a/src/backend/storage/aio/method_io_uring.c
+++ b/src/backend/storage/aio/method_io_uring.c
@@ -49,8 +49,8 @@
 
 
 /* Entry points for IoMethodOps. */
-static size_t pgaio_uring_shmem_size(void);
-static void pgaio_uring_shmem_init(bool first_time);
+static void pgaio_uring_shmem_request(void *arg);
+static void pgaio_uring_shmem_init(void *arg);
 static void pgaio_uring_init_backend(void);
 static int	pgaio_uring_submit(uint16 num_staged_ios, PgAioHandle **staged_ios);
 static void pgaio_uring_wait_one(PgAioHandle *ioh, uint64 ref_generation);
@@ -59,7 +59,6 @@ static void pgaio_uring_check_one(PgAioHandle *ioh, uint64 ref_generation);
 /* helper functions */
 static void pgaio_uring_sq_from_io(PgAioHandle *ioh, struct io_uring_sqe *sqe);
 
-
 const IoMethodOps pgaio_uring_ops = {
 	/*
 	 * While io_uring mostly is OK with FDs getting closed while the IO is in
@@ -70,8 +69,8 @@ const IoMethodOps pgaio_uring_ops = {
 	 */
 	.wait_on_fd_before_close = true,
 
-	.shmem_size = pgaio_uring_shmem_size,
-	.shmem_init = pgaio_uring_shmem_init,
+	.shmem_callbacks.request_fn = pgaio_uring_shmem_request,
+	.shmem_callbacks.init_fn = pgaio_uring_shmem_init,
 	.init_backend = pgaio_uring_init_backend,
 
 	.submit = pgaio_uring_submit,
@@ -267,23 +266,31 @@ pgaio_uring_shmem_size(void)
 {
 	size_t		sz;
 
+	sz = pgaio_uring_context_shmem_size();
+	sz = add_size(sz, pgaio_uring_ring_shmem_size());
+
+	return sz;
+}
+
+static void
+pgaio_uring_shmem_request(void *arg)
+{
 	/*
 	 * Kernel and liburing support for various features influences how much
 	 * shmem we need, perform the necessary checks.
 	 */
 	pgaio_uring_check_capabilities();
 
-	sz = pgaio_uring_context_shmem_size();
-	sz = add_size(sz, pgaio_uring_ring_shmem_size());
-
-	return sz;
+	ShmemRequestStruct(.name = "AioUringContext",
+					   .size = pgaio_uring_shmem_size(),
+					   .ptr = (void **) &pgaio_uring_contexts,
+		);
 }
 
 static void
-pgaio_uring_shmem_init(bool first_time)
+pgaio_uring_shmem_init(void *arg)
 {
 	int			TotalProcs = pgaio_uring_procs();
-	bool		found;
 	char	   *shmem;
 	size_t		ring_mem_remain = 0;
 	char	   *ring_mem_next = 0;
@@ -291,13 +298,11 @@ pgaio_uring_shmem_init(bool first_time)
 	/*
 	 * We allocate memory for all PgAioUringContext instances and, if
 	 * supported, the memory required for each of the io_uring instances, in
-	 * one ShmemInitStruct().
+	 * one combined allocation.
+	 *
+	 * pgaio_uring_contexts is already set to the base of the allocation.
 	 */
-	shmem = ShmemInitStruct("AioUringContext", pgaio_uring_shmem_size(), &found);
-	if (found)
-		return;
-
-	pgaio_uring_contexts = (PgAioUringContext *) shmem;
+	shmem = (char *) pgaio_uring_contexts;
 	shmem += pgaio_uring_context_shmem_size();
 
 	/* if supported, handle memory alignment / sizing for io_uring memory */
diff --git a/src/backend/storage/aio/method_worker.c b/src/backend/storage/aio/method_worker.c
index efe38e9f113..df94a434856 100644
--- a/src/backend/storage/aio/method_worker.c
+++ b/src/backend/storage/aio/method_worker.c
@@ -41,6 +41,7 @@
 #include "storage/ipc.h"
 #include "storage/latch.h"
 #include "storage/proc.h"
+#include "storage/shmem.h"
 #include "tcop/tcopprot.h"
 #include "utils/injection_point.h"
 #include "utils/memdebug.h"
@@ -73,16 +74,20 @@ typedef struct PgAioWorkerControl
 } PgAioWorkerControl;
 
 
-static size_t pgaio_worker_shmem_size(void);
-static void pgaio_worker_shmem_init(bool first_time);
+static void pgaio_worker_shmem_request(void *arg);
+static void pgaio_worker_shmem_init(void *arg);
+static void pgaio_worker_shmem_attach(void *arg);
+
+static PgAioWorkerSubmissionQueue *io_worker_submission_queue;
 
 static bool pgaio_worker_needs_synchronous_execution(PgAioHandle *ioh);
 static int	pgaio_worker_submit(uint16 num_staged_ios, PgAioHandle **staged_ios);
 
 
 const IoMethodOps pgaio_worker_ops = {
-	.shmem_size = pgaio_worker_shmem_size,
-	.shmem_init = pgaio_worker_shmem_init,
+	.shmem_callbacks.request_fn = pgaio_worker_shmem_request,
+	.shmem_callbacks.init_fn = pgaio_worker_shmem_init,
+	.shmem_callbacks.attach_fn = pgaio_worker_shmem_attach,
 
 	.needs_synchronous_execution = pgaio_worker_needs_synchronous_execution,
 	.submit = pgaio_worker_submit,
@@ -95,7 +100,6 @@ int			io_workers = 3;
 
 static int	io_worker_queue_size = 64;
 static int	MyIoWorkerId;
-static PgAioWorkerSubmissionQueue *io_worker_submission_queue;
 static PgAioWorkerControl *io_worker_control;
 
 
@@ -116,50 +120,60 @@ pgaio_worker_control_shmem_size(void)
 		sizeof(PgAioWorkerSlot) * MAX_IO_WORKERS;
 }
 
-static size_t
-pgaio_worker_shmem_size(void)
+/*
+ * Set secondary AIO worker pointer from the combined allocation.
+ */
+static void
+pgaio_worker_set_secondary_ptr(void)
 {
-	size_t		sz;
 	int			queue_size;
+	Size		queue_sz = pgaio_worker_queue_shmem_size(&queue_size);
 
-	sz = pgaio_worker_queue_shmem_size(&queue_size);
-	sz = add_size(sz, pgaio_worker_control_shmem_size());
-
-	return sz;
+	io_worker_control = (PgAioWorkerControl *)
+		((char *) io_worker_submission_queue + MAXALIGN(queue_sz));
 }
 
 static void
-pgaio_worker_shmem_init(bool first_time)
+pgaio_worker_shmem_init(void *arg)
 {
-	bool		found;
 	int			queue_size;
 
-	io_worker_submission_queue =
-		ShmemInitStruct("AioWorkerSubmissionQueue",
-						pgaio_worker_queue_shmem_size(&queue_size),
-						&found);
-	if (!found)
-	{
-		io_worker_submission_queue->size = queue_size;
-		io_worker_submission_queue->head = 0;
-		io_worker_submission_queue->tail = 0;
-	}
+	pgaio_worker_queue_shmem_size(&queue_size);
+	io_worker_submission_queue->size = queue_size;
+	io_worker_submission_queue->head = 0;
+	io_worker_submission_queue->tail = 0;
 
-	io_worker_control =
-		ShmemInitStruct("AioWorkerControl",
-						pgaio_worker_control_shmem_size(),
-						&found);
-	if (!found)
+	pgaio_worker_set_secondary_ptr();
+
+	io_worker_control->idle_worker_mask = 0;
+	for (int i = 0; i < MAX_IO_WORKERS; ++i)
 	{
-		io_worker_control->idle_worker_mask = 0;
-		for (int i = 0; i < MAX_IO_WORKERS; ++i)
-		{
-			io_worker_control->workers[i].latch = NULL;
-			io_worker_control->workers[i].in_use = false;
-		}
+		io_worker_control->workers[i].latch = NULL;
+		io_worker_control->workers[i].in_use = false;
 	}
 }
 
+static void
+pgaio_worker_shmem_attach(void *arg)
+{
+	pgaio_worker_set_secondary_ptr();
+}
+
+static void
+pgaio_worker_shmem_request(void *arg)
+{
+	size_t		size;
+	int			queue_size;
+
+	size = MAXALIGN(pgaio_worker_queue_shmem_size(&queue_size)) +
+		pgaio_worker_control_shmem_size();
+
+	ShmemRequestStruct(.name = "AioWorkerSubmissionQueue",
+					   .size = size,
+					   .ptr = (void **) &io_worker_submission_queue,
+		);
+}
+
 static int
 pgaio_worker_choose_idle(void)
 {
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 7a8c69de802..a510c928daa 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -122,7 +122,6 @@ CalculateShmemSize(void)
 	size = add_size(size, WaitEventCustomShmemSize());
 	size = add_size(size, InjectionPointShmemSize());
 	size = add_size(size, SlotSyncShmemSize());
-	size = add_size(size, AioShmemSize());
 	size = add_size(size, WaitLSNShmemSize());
 	size = add_size(size, LogicalDecodingCtlShmemSize());
 	size = add_size(size, DataChecksumsShmemSize());
@@ -301,7 +300,6 @@ CreateOrAttachShmemStructs(void)
 	StatsShmemInit();
 	WaitEventCustomShmemInit();
 	InjectionPointShmemInit();
-	AioShmemInit();
 	WaitLSNShmemInit();
 	LogicalDecodingCtlShmemInit();
 }
diff --git a/src/include/storage/aio_internal.h b/src/include/storage/aio_internal.h
index 33e1e2dc048..9ca4087aa7f 100644
--- a/src/include/storage/aio_internal.h
+++ b/src/include/storage/aio_internal.h
@@ -20,6 +20,8 @@
 #include "port/pg_iovec.h"
 #include "storage/aio.h"
 #include "storage/condition_variable.h"
+#include "storage/ipc.h"
+#include "storage/shmem.h"
 
 
 /*
@@ -267,20 +269,8 @@ typedef struct IoMethodOps
 	 */
 	bool		wait_on_fd_before_close;
 
-
 	/* global initialization */
-
-	/*
-	 * Amount of additional shared memory to reserve for the io_method. Called
-	 * just like a normal ipci.c style *Size() function. Optional.
-	 */
-	size_t		(*shmem_size) (void);
-
-	/*
-	 * Initialize shared memory. First time is true if AIO's shared memory was
-	 * just initialized, false otherwise. Optional.
-	 */
-	void		(*shmem_init) (bool first_time);
+	ShmemCallbacks shmem_callbacks;
 
 	/*
 	 * Per-backend initialization. Optional.
diff --git a/src/include/storage/aio_subsys.h b/src/include/storage/aio_subsys.h
index 276cb3e31c4..dd54869351f 100644
--- a/src/include/storage/aio_subsys.h
+++ b/src/include/storage/aio_subsys.h
@@ -20,12 +20,8 @@
 
 
 /* aio_init.c */
-extern Size AioShmemSize(void);
-extern void AioShmemInit(void);
-
 extern void pgaio_init_backend(void);
 
-
 /* aio.c */
 extern void pgaio_error_cleanup(void);
 extern void AtEOXact_Aio(bool is_commit);
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index c199f18a27a..b438794d46d 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -53,3 +53,6 @@ PG_SHMEM_SUBSYSTEM(ProcSignalShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(AsyncShmemCallbacks)
+
+/* AIO subsystem. This delegates to the method-specific callbacks */
+PG_SHMEM_SUBSYSTEM(AioShmemCallbacks)
-- 
2.34.1



  [text/x-patch] v20260405-0012-Add-option-for-aligning-shmem-allocations.patch (4.0K, 13-v20260405-0012-Add-option-for-aligning-shmem-allocations.patch)
  download | inline diff:
From 2b257c8c05d2f12d00c36e0d45a180c19990bae2 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Sat, 21 Mar 2026 23:44:15 +0200
Subject: [PATCH v20260405 12/15] Add option for aligning shmem allocations

The buffer blocks (in the next commit) are IO-aligned. This might come
handy in other places too, so make it an explicit feature of
ShmemRequestStruct.
---
 src/backend/storage/ipc/shmem.c | 26 ++++++++++++++++----------
 src/include/storage/shmem.h     |  6 ++++++
 2 files changed, 22 insertions(+), 10 deletions(-)

diff --git a/src/backend/storage/ipc/shmem.c b/src/backend/storage/ipc/shmem.c
index bc186d6ea17..973811e545e 100644
--- a/src/backend/storage/ipc/shmem.c
+++ b/src/backend/storage/ipc/shmem.c
@@ -239,7 +239,7 @@ typedef struct ShmemAllocatorData
 
 #define ShmemIndexLock (&ShmemAllocator->index_lock)
 
-static void *ShmemAllocRaw(Size size, Size *allocated_size);
+static void *ShmemAllocRaw(Size size, Size alignment, Size *allocated_size);
 
 /* shared memory global variables */
 
@@ -400,7 +400,8 @@ ShmemGetRequestedSize(void)
 	{
 		size = add_size(size, request->options->size);
 		/* calculate alignment padding like ShmemAllocRaw() does */
-		size = CACHELINEALIGN(size);
+		size = TYPEALIGN(Max(request->options->alignment, PG_CACHE_LINE_SIZE),
+						 size);
 	}
 
 	return size;
@@ -525,7 +526,9 @@ 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.
 	 */
-	structPtr = ShmemAllocRaw(request->options->size, &allocated_size);
+	structPtr = ShmemAllocRaw(request->options->size,
+							  request->options->alignment,
+							  &allocated_size);
 	if (structPtr == NULL)
 	{
 		/* out of memory; remove the failed ShmemIndex entry */
@@ -754,7 +757,7 @@ ShmemAlloc(Size size)
 	void	   *newSpace;
 	Size		allocated_size;
 
-	newSpace = ShmemAllocRaw(size, &allocated_size);
+	newSpace = ShmemAllocRaw(size, 0, &allocated_size);
 	if (!newSpace)
 		ereport(ERROR,
 				(errcode(ERRCODE_OUT_OF_MEMORY),
@@ -773,7 +776,7 @@ ShmemAllocNoError(Size size)
 {
 	Size		allocated_size;
 
-	return ShmemAllocRaw(size, &allocated_size);
+	return ShmemAllocRaw(size, 0, &allocated_size);
 }
 
 /*
@@ -783,8 +786,9 @@ ShmemAllocNoError(Size size)
  * be equal to the number requested plus any padding we choose to add.
  */
 static void *
-ShmemAllocRaw(Size size, Size *allocated_size)
+ShmemAllocRaw(Size size, Size alignment, Size *allocated_size)
 {
+	Size		rawStart;
 	Size		newStart;
 	Size		newFree;
 	void	   *newSpace;
@@ -800,14 +804,15 @@ ShmemAllocRaw(Size size, Size *allocated_size)
 	 * structures out to a power-of-two size - but without this, even that
 	 * won't be sufficient.
 	 */
-	size = CACHELINEALIGN(size);
-	*allocated_size = size;
+	if (alignment < PG_CACHE_LINE_SIZE)
+		alignment = PG_CACHE_LINE_SIZE;
 
 	Assert(ShmemSegHdr != NULL);
 
 	SpinLockAcquire(&ShmemAllocator->shmem_lock);
 
-	newStart = ShmemAllocator->free_offset;
+	rawStart = ShmemAllocator->free_offset;
+	newStart = TYPEALIGN(alignment, rawStart);
 
 	newFree = newStart + size;
 	if (newFree <= ShmemSegHdr->totalsize)
@@ -821,8 +826,9 @@ ShmemAllocRaw(Size size, Size *allocated_size)
 	SpinLockRelease(&ShmemAllocator->shmem_lock);
 
 	/* note this assert is okay with newSpace == NULL */
-	Assert(newSpace == (void *) CACHELINEALIGN(newSpace));
+	Assert(newSpace == (void *) TYPEALIGN(alignment, newSpace));
 
+	*allocated_size = newFree - rawStart;
 	return newSpace;
 }
 
diff --git a/src/include/storage/shmem.h b/src/include/storage/shmem.h
index 147a6915f7e..91218db6d6e 100644
--- a/src/include/storage/shmem.h
+++ b/src/include/storage/shmem.h
@@ -51,6 +51,12 @@ typedef struct ShmemStructOpts
 	 */
 	ssize_t		size;
 
+	/*
+	 * Alignment of the starting address. If not set, defaults to cacheline
+	 * boundary.  Must be a power of two.
+	 */
+	size_t		alignment;
+
 	/*
 	 * 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
-- 
2.34.1



  [text/x-patch] v20260405-0013-Convert-buffer-manager-to-the-new-API.patch (15.6K, 14-v20260405-0013-Convert-buffer-manager-to-the-new-API.patch)
  download | inline diff:
From 5709c7a77a1ab3692fefa6d4c0c0aaf2b4b7609d Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Thu, 2 Apr 2026 00:44:02 +0300
Subject: [PATCH v20260405 13/15] Convert buffer manager to the new API

---
 src/backend/storage/buffer/buf_init.c  | 149 ++++++++++---------------
 src/backend/storage/buffer/buf_table.c |  54 +++++----
 src/backend/storage/buffer/freelist.c  |  93 +++++----------
 src/backend/storage/ipc/ipci.c         |   3 -
 src/include/storage/buf_internals.h    |   5 -
 src/include/storage/bufmgr.h           |   4 -
 src/include/storage/subsystemlist.h    |   3 +
 7 files changed, 124 insertions(+), 187 deletions(-)

diff --git a/src/backend/storage/buffer/buf_init.c b/src/backend/storage/buffer/buf_init.c
index c0c223b2e32..1407c930c56 100644
--- a/src/backend/storage/buffer/buf_init.c
+++ b/src/backend/storage/buffer/buf_init.c
@@ -18,6 +18,8 @@
 #include "storage/buf_internals.h"
 #include "storage/bufmgr.h"
 #include "storage/proclist.h"
+#include "storage/shmem.h"
+#include "storage/subsystems.h"
 
 BufferDescPadded *BufferDescriptors;
 char	   *BufferBlocks;
@@ -25,6 +27,15 @@ ConditionVariableMinimallyPadded *BufferIOCVArray;
 WritebackContext BackendWritebackContext;
 CkptSortItem *CkptBufferIds;
 
+static void BufferManagerShmemRequest(void *arg);
+static void BufferManagerShmemInit(void *arg);
+static void BufferManagerShmemAttach(void *arg);
+
+const ShmemCallbacks BufferManagerShmemCallbacks = {
+	.request_fn = BufferManagerShmemRequest,
+	.init_fn = BufferManagerShmemInit,
+	.attach_fn = BufferManagerShmemAttach,
+};
 
 /*
  * Data Structures:
@@ -60,37 +71,31 @@ CkptSortItem *CkptBufferIds;
 
 
 /*
- * Initialize shared buffer pool
- *
- * This is called once during shared-memory initialization (either in the
- * postmaster, or in a standalone backend).
+ * Register shared memory area for the buffer pool.
  */
-void
-BufferManagerShmemInit(void)
+static void
+BufferManagerShmemRequest(void *arg)
 {
-	bool		foundBufs,
-				foundDescs,
-				foundIOCV,
-				foundBufCkpt;
-
+	ShmemRequestStruct(.name = "Buffer Descriptors",
+					   .size = NBuffers * sizeof(BufferDescPadded),
 	/* Align descriptors to a cacheline boundary. */
-	BufferDescriptors = (BufferDescPadded *)
-		ShmemInitStruct("Buffer Descriptors",
-						NBuffers * sizeof(BufferDescPadded),
-						&foundDescs);
+					   .alignment = PG_CACHE_LINE_SIZE,
+					   .ptr = (void **) &BufferDescriptors,
+		);
 
+	ShmemRequestStruct(.name = "Buffer Blocks",
+					   .size = NBuffers * (Size) BLCKSZ,
 	/* Align buffer pool on IO page size boundary. */
-	BufferBlocks = (char *)
-		TYPEALIGN(PG_IO_ALIGN_SIZE,
-				  ShmemInitStruct("Buffer Blocks",
-								  NBuffers * (Size) BLCKSZ + PG_IO_ALIGN_SIZE,
-								  &foundBufs));
-
-	/* Align condition variables to cacheline boundary. */
-	BufferIOCVArray = (ConditionVariableMinimallyPadded *)
-		ShmemInitStruct("Buffer IO Condition Variables",
-						NBuffers * sizeof(ConditionVariableMinimallyPadded),
-						&foundIOCV);
+					   .alignment = PG_IO_ALIGN_SIZE,
+					   .ptr = (void **) &BufferBlocks,
+		);
+
+	ShmemRequestStruct(.name = "Buffer IO Condition Variables",
+					   .size = NBuffers * sizeof(ConditionVariableMinimallyPadded),
+	/* Align descriptors to a cacheline boundary. */
+					   .alignment = PG_CACHE_LINE_SIZE,
+					   .ptr = (void **) &BufferIOCVArray,
+		);
 
 	/*
 	 * The array used to sort to-be-checkpointed buffer ids is located in
@@ -99,80 +104,50 @@ BufferManagerShmemInit(void)
 	 * the checkpointer is restarted, memory allocation failures would be
 	 * painful.
 	 */
-	CkptBufferIds = (CkptSortItem *)
-		ShmemInitStruct("Checkpoint BufferIds",
-						NBuffers * sizeof(CkptSortItem), &foundBufCkpt);
+	ShmemRequestStruct(.name = "Checkpoint BufferIds",
+					   .size = NBuffers * sizeof(CkptSortItem),
+					   .ptr = (void **) &CkptBufferIds,
+		);
+}
 
-	if (foundDescs || foundBufs || foundIOCV || foundBufCkpt)
-	{
-		/* should find all of these, or none of them */
-		Assert(foundDescs && foundBufs && foundIOCV && foundBufCkpt);
-		/* note: this path is only taken in EXEC_BACKEND case */
-	}
-	else
+/*
+ * Initialize shared buffer pool
+ *
+ * This is called once during shared-memory initialization (either in the
+ * postmaster, or in a standalone backend).
+ */
+static void
+BufferManagerShmemInit(void *arg)
+{
+	/*
+	 * Initialize all the buffer headers.
+	 */
+	for (int i = 0; i < NBuffers; i++)
 	{
-		int			i;
+		BufferDesc *buf = GetBufferDescriptor(i);
 
-		/*
-		 * Initialize all the buffer headers.
-		 */
-		for (i = 0; i < NBuffers; i++)
-		{
-			BufferDesc *buf = GetBufferDescriptor(i);
+		ClearBufferTag(&buf->tag);
 
-			ClearBufferTag(&buf->tag);
+		pg_atomic_init_u64(&buf->state, 0);
+		buf->wait_backend_pgprocno = INVALID_PROC_NUMBER;
 
-			pg_atomic_init_u64(&buf->state, 0);
-			buf->wait_backend_pgprocno = INVALID_PROC_NUMBER;
+		buf->buf_id = i;
 
-			buf->buf_id = i;
+		pgaio_wref_clear(&buf->io_wref);
 
-			pgaio_wref_clear(&buf->io_wref);
-
-			proclist_init(&buf->lock_waiters);
-			ConditionVariableInit(BufferDescriptorGetIOCV(buf));
-		}
+		proclist_init(&buf->lock_waiters);
+		ConditionVariableInit(BufferDescriptorGetIOCV(buf));
 	}
 
-	/* Init other shared buffer-management stuff */
-	StrategyInitialize(!foundDescs);
-
 	/* Initialize per-backend file flush context */
 	WritebackContextInit(&BackendWritebackContext,
 						 &backend_flush_after);
 }
 
-/*
- * BufferManagerShmemSize
- *
- * compute the size of shared memory for the buffer pool including
- * data pages, buffer descriptors, hash tables, etc.
- */
-Size
-BufferManagerShmemSize(void)
+static void
+BufferManagerShmemAttach(void *arg)
 {
-	Size		size = 0;
-
-	/* size of buffer descriptors */
-	size = add_size(size, mul_size(NBuffers, sizeof(BufferDescPadded)));
-	/* to allow aligning buffer descriptors */
-	size = add_size(size, PG_CACHE_LINE_SIZE);
-
-	/* size of data pages, plus alignment padding */
-	size = add_size(size, PG_IO_ALIGN_SIZE);
-	size = add_size(size, mul_size(NBuffers, BLCKSZ));
-
-	/* size of stuff controlled by freelist.c */
-	size = add_size(size, StrategyShmemSize());
-
-	/* size of I/O condition variables */
-	size = add_size(size, mul_size(NBuffers,
-								   sizeof(ConditionVariableMinimallyPadded)));
-	/* to allow aligning the above */
-	size = add_size(size, PG_CACHE_LINE_SIZE);
-
-	/* size of checkpoint sort array in bufmgr.c */
-	size = add_size(size, mul_size(NBuffers, sizeof(CkptSortItem)));
-
-	return size;
+	/* Initialize per-backend file flush context */
+	WritebackContextInit(&BackendWritebackContext,
+						 &backend_flush_after);
 }
diff --git a/src/backend/storage/buffer/buf_table.c b/src/backend/storage/buffer/buf_table.c
index d04ef74b850..347bf267d73 100644
--- a/src/backend/storage/buffer/buf_table.c
+++ b/src/backend/storage/buffer/buf_table.c
@@ -22,6 +22,7 @@
 #include "postgres.h"
 
 #include "storage/buf_internals.h"
+#include "storage/subsystems.h"
 
 /* entry for buffer lookup hashtable */
 typedef struct
@@ -32,37 +33,42 @@ typedef struct
 
 static HTAB *SharedBufHash;
 
+static void BufTableShmemRequest(void *arg);
 
-/*
- * Estimate space needed for mapping hashtable
- *		size is the desired hash table size (possibly more than NBuffers)
- */
-Size
-BufTableShmemSize(int size)
-{
-	return hash_estimate_size(size, sizeof(BufferLookupEnt));
-}
+const ShmemCallbacks BufTableShmemCallbacks = {
+	.request_fn = BufTableShmemRequest,
+	/* no special initialization needed, the hash table will start empty */
+};
 
 /*
- * Initialize shmem hash table for mapping buffers
+ * Register shmem hash table for mapping buffers.
  *		size is the desired hash table size (possibly more than NBuffers)
  */
 void
-InitBufTable(int size)
+BufTableShmemRequest(void *arg)
 {
-	HASHCTL		info;
-
-	/* assume no locking is needed yet */
-
-	/* BufferTag maps to Buffer */
-	info.keysize = sizeof(BufferTag);
-	info.entrysize = sizeof(BufferLookupEnt);
-	info.num_partitions = NUM_BUFFER_PARTITIONS;
-
-	SharedBufHash = ShmemInitHash("Shared Buffer Lookup Table",
-								  size,
-								  &info,
-								  HASH_ELEM | HASH_BLOBS | HASH_PARTITION | HASH_FIXED_SIZE);
+	int			size;
+
+	/*
+	 * Request the shared buffer lookup hashtable.
+	 *
+	 * Since we can't tolerate running out of lookup table entries, we must be
+	 * sure to specify an adequate table size here.  The maximum steady-state
+	 * usage is of course NBuffers entries, but BufferAlloc() tries to insert
+	 * a new entry before deleting the old.  In principle this could be
+	 * happening in each partition concurrently, so we could need as many as
+	 * NBuffers + NUM_BUFFER_PARTITIONS entries.
+	 */
+	size = NBuffers + NUM_BUFFER_PARTITIONS;
+
+	ShmemRequestHash(.name = "Shared Buffer Lookup Table",
+					 .nelems = size,
+					 .ptr = &SharedBufHash,
+					 .hash_info.keysize = sizeof(BufferTag),
+					 .hash_info.entrysize = sizeof(BufferLookupEnt),
+					 .hash_info.num_partitions = NUM_BUFFER_PARTITIONS,
+					 .hash_flags = HASH_ELEM | HASH_BLOBS | HASH_PARTITION | HASH_FIXED_SIZE,
+		);
 }
 
 /*
diff --git a/src/backend/storage/buffer/freelist.c b/src/backend/storage/buffer/freelist.c
index b7687836188..fdb5bad7910 100644
--- a/src/backend/storage/buffer/freelist.c
+++ b/src/backend/storage/buffer/freelist.c
@@ -20,6 +20,8 @@
 #include "storage/buf_internals.h"
 #include "storage/bufmgr.h"
 #include "storage/proc.h"
+#include "storage/shmem.h"
+#include "storage/subsystems.h"
 
 #define INT_ACCESS_ONCE(var)	((int)(*((volatile int *)&(var))))
 
@@ -56,6 +58,14 @@ typedef struct
 /* Pointers to shared state */
 static BufferStrategyControl *StrategyControl = NULL;
 
+static void StrategyCtlShmemRequest(void *arg);
+static void StrategyCtlShmemInit(void *arg);
+
+const ShmemCallbacks StrategyCtlShmemCallbacks = {
+	.request_fn = StrategyCtlShmemRequest,
+	.init_fn = StrategyCtlShmemInit,
+};
+
 /*
  * Private (non-shared) state for managing a ring of shared buffers to re-use.
  * This is currently the only kind of BufferAccessStrategy object, but someday
@@ -369,80 +379,35 @@ StrategyNotifyBgWriter(int bgwprocno)
 
 
 /*
- * StrategyShmemSize
- *
- * estimate the size of shared memory used by the freelist-related structures.
- *
- * Note: for somewhat historical reasons, the buffer lookup hashtable size
- * is also determined here.
+ * StrategyCtlShmemRequest -- request shared memory for the buffer
+ *		cache replacement strategy.
  */
-Size
-StrategyShmemSize(void)
+static void
+StrategyCtlShmemRequest(void *arg)
 {
-	Size		size = 0;
-
-	/* size of lookup hash table ... see comment in StrategyInitialize */
-	size = add_size(size, BufTableShmemSize(NBuffers + NUM_BUFFER_PARTITIONS));
-
-	/* size of the shared replacement strategy control block */
-	size = add_size(size, MAXALIGN(sizeof(BufferStrategyControl)));
-
-	return size;
+	ShmemRequestStruct(.name = "Buffer Strategy Status",
+					   .size = sizeof(BufferStrategyControl),
+					   .ptr = (void **) &StrategyControl
+		);
 }
 
 /*
- * StrategyInitialize -- initialize the buffer cache replacement
- *		strategy.
- *
- * Assumes: All of the buffers are already built into a linked list.
- *		Only called by postmaster and only during initialization.
+ * StrategyCtlShmemInit -- initialize the buffer cache replacement strategy.
  */
-void
-StrategyInitialize(bool init)
+static void
+StrategyCtlShmemInit(void *arg)
 {
-	bool		found;
+	SpinLockInit(&StrategyControl->buffer_strategy_lock);
 
-	/*
-	 * Initialize the shared buffer lookup hashtable.
-	 *
-	 * Since we can't tolerate running out of lookup table entries, we must be
-	 * sure to specify an adequate table size here.  The maximum steady-state
-	 * usage is of course NBuffers entries, but BufferAlloc() tries to insert
-	 * a new entry before deleting the old.  In principle this could be
-	 * happening in each partition concurrently, so we could need as many as
-	 * NBuffers + NUM_BUFFER_PARTITIONS entries.
-	 */
-	InitBufTable(NBuffers + NUM_BUFFER_PARTITIONS);
-
-	/*
-	 * Get or create the shared strategy control block
-	 */
-	StrategyControl = (BufferStrategyControl *)
-		ShmemInitStruct("Buffer Strategy Status",
-						sizeof(BufferStrategyControl),
-						&found);
-
-	if (!found)
-	{
-		/*
-		 * Only done once, usually in postmaster
-		 */
-		Assert(init);
-
-		SpinLockInit(&StrategyControl->buffer_strategy_lock);
+	/* Initialize the clock-sweep pointer */
+	pg_atomic_init_u32(&StrategyControl->nextVictimBuffer, 0);
 
-		/* Initialize the clock-sweep pointer */
-		pg_atomic_init_u32(&StrategyControl->nextVictimBuffer, 0);
+	/* Clear statistics */
+	StrategyControl->completePasses = 0;
+	pg_atomic_init_u32(&StrategyControl->numBufferAllocs, 0);
 
-		/* Clear statistics */
-		StrategyControl->completePasses = 0;
-		pg_atomic_init_u32(&StrategyControl->numBufferAllocs, 0);
-
-		/* No pending notification */
-		StrategyControl->bgwprocno = -1;
-	}
-	else
-		Assert(!init);
+	/* No pending notification */
+	StrategyControl->bgwprocno = -1;
 }
 
 
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index a510c928daa..f64c1d59fa3 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -39,7 +39,6 @@
 #include "replication/walreceiver.h"
 #include "replication/walsender.h"
 #include "storage/aio_subsys.h"
-#include "storage/bufmgr.h"
 #include "storage/dsm.h"
 #include "storage/ipc.h"
 #include "storage/pg_shmem.h"
@@ -99,7 +98,6 @@ CalculateShmemSize(void)
 	size = add_size(size, ShmemGetRequestedSize());
 
 	/* legacy subsystems */
-	size = add_size(size, BufferManagerShmemSize());
 	size = add_size(size, LockManagerShmemSize());
 	size = add_size(size, XLogPrefetchShmemSize());
 	size = add_size(size, XLOGShmemSize());
@@ -263,7 +261,6 @@ CreateOrAttachShmemStructs(void)
 	XLOGShmemInit();
 	XLogPrefetchShmemInit();
 	XLogRecoveryShmemInit();
-	BufferManagerShmemInit();
 
 	/*
 	 * Set up lock manager
diff --git a/src/include/storage/buf_internals.h b/src/include/storage/buf_internals.h
index ad1b7b2216a..89615a254a3 100644
--- a/src/include/storage/buf_internals.h
+++ b/src/include/storage/buf_internals.h
@@ -587,12 +587,7 @@ extern bool StrategyRejectBuffer(BufferAccessStrategy strategy,
 extern int	StrategySyncStart(uint32 *complete_passes, uint32 *num_buf_alloc);
 extern void StrategyNotifyBgWriter(int bgwprocno);
 
-extern Size StrategyShmemSize(void);
-extern void StrategyInitialize(bool init);
-
 /* buf_table.c */
-extern Size BufTableShmemSize(int size);
-extern void InitBufTable(int size);
 extern uint32 BufTableHashCode(BufferTag *tagPtr);
 extern int	BufTableLookup(BufferTag *tagPtr, uint32 hashcode);
 extern int	BufTableInsert(BufferTag *tagPtr, uint32 hashcode, int buf_id);
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index aa61a39d9e6..6837b35fc6d 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -371,10 +371,6 @@ extern void MarkDirtyAllUnpinnedBuffers(int32 *buffers_dirtied,
 										int32 *buffers_already_dirty,
 										int32 *buffers_skipped);
 
-/* in buf_init.c */
-extern void BufferManagerShmemInit(void);
-extern Size BufferManagerShmemSize(void);
-
 /* in localbuf.c */
 extern void AtProcExit_LocalBuffers(void);
 
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index b438794d46d..d8e11756a61 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -36,6 +36,9 @@ PG_SHMEM_SUBSYSTEM(CLOGShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(CommitTsShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SUBTRANSShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(MultiXactShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(BufferManagerShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(StrategyCtlShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(BufTableShmemCallbacks)
 
 /* predicate lock manager */
 PG_SHMEM_SUBSYSTEM(PredicateLockShmemCallbacks)
-- 
2.34.1



  [text/x-patch] v20260405-0014-Convert-all-remaining-subsystems-to-use-th.patch (110.5K, 15-v20260405-0014-Convert-all-remaining-subsystems-to-use-th.patch)
  download | inline diff:
From bd06717034dc254e81048d679b444fb596fceb00 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Sat, 21 Mar 2026 19:05:26 +0200
Subject: [PATCH v20260405 14/15] Convert all remaining subsystems to use the
 new API

---
 src/backend/access/common/syncscan.c          |  76 ++++----
 src/backend/access/nbtree/nbtutils.c          |  54 +++---
 src/backend/access/transam/twophase.c         |  75 ++++----
 src/backend/access/transam/xlog.c             |  82 +++++----
 src/backend/access/transam/xlogprefetcher.c   |  51 +++---
 src/backend/access/transam/xlogrecovery.c     |  35 ++--
 src/backend/access/transam/xlogwait.c         |  50 ++---
 src/backend/postmaster/autovacuum.c           |  79 ++++----
 src/backend/postmaster/bgworker.c             | 105 +++++------
 src/backend/postmaster/checkpointer.c         |  56 +++---
 src/backend/postmaster/datachecksum_state.c   |  41 ++---
 src/backend/postmaster/pgarch.c               |  43 +++--
 src/backend/postmaster/walsummarizer.c        |  60 +++---
 src/backend/replication/logical/launcher.c    |  56 +++---
 src/backend/replication/logical/logicalctl.c  |  29 ++-
 src/backend/replication/logical/origin.c      |  59 +++---
 src/backend/replication/logical/slotsync.c    |  41 +++--
 src/backend/replication/slot.c                |  64 +++----
 src/backend/replication/walreceiverfuncs.c    |  51 +++---
 src/backend/replication/walsender.c           |  59 +++---
 src/backend/storage/ipc/ipci.c                | 124 +------------
 src/backend/storage/lmgr/lock.c               | 113 +++++-------
 src/backend/utils/activity/backend_status.c   | 173 +++++++-----------
 src/backend/utils/activity/pgstat_shmem.c     | 158 ++++++++--------
 src/backend/utils/activity/wait_event.c       |  83 ++++-----
 src/backend/utils/misc/injection_point.c      |  57 +++---
 src/include/access/nbtree.h                   |   2 -
 src/include/access/syncscan.h                 |   2 -
 src/include/access/twophase.h                 |   3 -
 src/include/access/xlog.h                     |   2 -
 src/include/access/xlogprefetcher.h           |   3 -
 src/include/access/xlogrecovery.h             |   3 -
 src/include/access/xlogwait.h                 |   2 -
 src/include/pgstat.h                          |   4 -
 src/include/postmaster/autovacuum.h           |   4 -
 src/include/postmaster/bgworker_internals.h   |   2 -
 src/include/postmaster/bgwriter.h             |   3 -
 src/include/postmaster/datachecksum_state.h   |   4 -
 src/include/postmaster/pgarch.h               |   2 -
 src/include/postmaster/walsummarizer.h        |   2 -
 src/include/replication/logicalctl.h          |   2 -
 src/include/replication/logicallauncher.h     |   3 -
 src/include/replication/origin.h              |   4 -
 src/include/replication/slot.h                |   4 -
 src/include/replication/slotsync.h            |   2 -
 src/include/replication/walreceiver.h         |   2 -
 src/include/replication/walsender.h           |   2 -
 src/include/storage/lock.h                    |   2 -
 src/include/storage/subsystemlist.h           |  27 +++
 src/include/utils/backend_status.h            |   8 -
 src/include/utils/injection_point.h           |   3 -
 src/include/utils/wait_event.h                |   2 -
 .../injection_points/injection_points.c       |  59 ++----
 src/test/modules/test_aio/test_aio.c          | 107 +++++------
 54 files changed, 933 insertions(+), 1206 deletions(-)

diff --git a/src/backend/access/common/syncscan.c b/src/backend/access/common/syncscan.c
index 6fcfcb0e560..0f9eb167bed 100644
--- a/src/backend/access/common/syncscan.c
+++ b/src/backend/access/common/syncscan.c
@@ -50,6 +50,7 @@
 #include "miscadmin.h"
 #include "storage/lwlock.h"
 #include "storage/shmem.h"
+#include "storage/subsystems.h"
 #include "utils/rel.h"
 
 
@@ -111,6 +112,14 @@ typedef struct ss_scan_locations_t
 #define SizeOfScanLocations(N) \
 	(offsetof(ss_scan_locations_t, items) + (N) * sizeof(ss_lru_item_t))
 
+static void SyncScanShmemRequest(void *arg);
+static void SyncScanShmemInit(void *arg);
+
+const ShmemCallbacks SyncScanShmemCallbacks = {
+	.request_fn = SyncScanShmemRequest,
+	.init_fn = SyncScanShmemInit,
+};
+
 /* Pointer to struct in shared memory */
 static ss_scan_locations_t *scan_locations;
 
@@ -120,58 +129,47 @@ static BlockNumber ss_search(RelFileLocator relfilelocator,
 
 
 /*
- * SyncScanShmemSize --- report amount of shared memory space needed
+ * SyncScanShmemRequest --- register this module's shared memory
  */
-Size
-SyncScanShmemSize(void)
+static void
+SyncScanShmemRequest(void *arg)
 {
-	return SizeOfScanLocations(SYNC_SCAN_NELEM);
+	ShmemRequestStruct(.name = "Sync Scan Locations List",
+					   .size = SizeOfScanLocations(SYNC_SCAN_NELEM),
+					   .ptr = (void **) &scan_locations,
+		);
 }
 
 /*
  * SyncScanShmemInit --- initialize this module's shared memory
  */
-void
-SyncScanShmemInit(void)
+static void
+SyncScanShmemInit(void *arg)
 {
 	int			i;
-	bool		found;
 
-	scan_locations = (ss_scan_locations_t *)
-		ShmemInitStruct("Sync Scan Locations List",
-						SizeOfScanLocations(SYNC_SCAN_NELEM),
-						&found);
+	scan_locations->head = &scan_locations->items[0];
+	scan_locations->tail = &scan_locations->items[SYNC_SCAN_NELEM - 1];
 
-	if (!IsUnderPostmaster)
+	for (i = 0; i < SYNC_SCAN_NELEM; i++)
 	{
-		/* Initialize shared memory area */
-		Assert(!found);
-
-		scan_locations->head = &scan_locations->items[0];
-		scan_locations->tail = &scan_locations->items[SYNC_SCAN_NELEM - 1];
-
-		for (i = 0; i < SYNC_SCAN_NELEM; i++)
-		{
-			ss_lru_item_t *item = &scan_locations->items[i];
-
-			/*
-			 * Initialize all slots with invalid values. As scans are started,
-			 * these invalid entries will fall off the LRU list and get
-			 * replaced with real entries.
-			 */
-			item->location.relfilelocator.spcOid = InvalidOid;
-			item->location.relfilelocator.dbOid = InvalidOid;
-			item->location.relfilelocator.relNumber = InvalidRelFileNumber;
-			item->location.location = InvalidBlockNumber;
-
-			item->prev = (i > 0) ?
-				(&scan_locations->items[i - 1]) : NULL;
-			item->next = (i < SYNC_SCAN_NELEM - 1) ?
-				(&scan_locations->items[i + 1]) : NULL;
-		}
+		ss_lru_item_t *item = &scan_locations->items[i];
+
+		/*
+		 * Initialize all slots with invalid values. As scans are started,
+		 * these invalid entries will fall off the LRU list and get replaced
+		 * with real entries.
+		 */
+		item->location.relfilelocator.spcOid = InvalidOid;
+		item->location.relfilelocator.dbOid = InvalidOid;
+		item->location.relfilelocator.relNumber = InvalidRelFileNumber;
+		item->location.location = InvalidBlockNumber;
+
+		item->prev = (i > 0) ?
+			(&scan_locations->items[i - 1]) : NULL;
+		item->next = (i < SYNC_SCAN_NELEM - 1) ?
+			(&scan_locations->items[i + 1]) : NULL;
 	}
-	else
-		Assert(found);
 }
 
 /*
diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c
index 732bc750c9e..014faa1622f 100644
--- a/src/backend/access/nbtree/nbtutils.c
+++ b/src/backend/access/nbtree/nbtutils.c
@@ -25,6 +25,7 @@
 #include "lib/qunique.h"
 #include "miscadmin.h"
 #include "storage/lwlock.h"
+#include "storage/subsystems.h"
 #include "utils/datum.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
@@ -417,6 +418,13 @@ typedef struct BTVacInfo
 
 static BTVacInfo *btvacinfo;
 
+static void BTreeShmemRequest(void *arg);
+static void BTreeShmemInit(void *arg);
+
+const ShmemCallbacks BTreeShmemCallbacks = {
+	.request_fn = BTreeShmemRequest,
+	.init_fn = BTreeShmemInit,
+};
 
 /*
  * _bt_vacuum_cycleid --- get the active vacuum cycle ID for an index,
@@ -553,47 +561,37 @@ _bt_end_vacuum_callback(int code, Datum arg)
 }
 
 /*
- * BTreeShmemSize --- report amount of shared memory space needed
+ * BTreeShmemRequest --- register this module's shared memory
  */
-Size
-BTreeShmemSize(void)
+static void
+BTreeShmemRequest(void *arg)
 {
 	Size		size;
 
 	size = offsetof(BTVacInfo, vacuums);
 	size = add_size(size, mul_size(MaxBackends, sizeof(BTOneVacInfo)));
-	return size;
+
+	ShmemRequestStruct(.name = "BTree Vacuum State",
+					   .size = size,
+					   .ptr = (void **) &btvacinfo,
+		);
 }
 
 /*
  * BTreeShmemInit --- initialize this module's shared memory
  */
-void
-BTreeShmemInit(void)
+static void
+BTreeShmemInit(void *arg)
 {
-	bool		found;
-
-	btvacinfo = (BTVacInfo *) ShmemInitStruct("BTree Vacuum State",
-											  BTreeShmemSize(),
-											  &found);
-
-	if (!IsUnderPostmaster)
-	{
-		/* Initialize shared memory area */
-		Assert(!found);
-
-		/*
-		 * It doesn't really matter what the cycle counter starts at, but
-		 * having it always start the same doesn't seem good.  Seed with
-		 * low-order bits of time() instead.
-		 */
-		btvacinfo->cycle_ctr = (BTCycleId) time(NULL);
+	/*
+	 * It doesn't really matter what the cycle counter starts at, but having
+	 * it always start the same doesn't seem good.  Seed with low-order bits
+	 * of time() instead.
+	 */
+	btvacinfo->cycle_ctr = (BTCycleId) time(NULL);
 
-		btvacinfo->num_vacuums = 0;
-		btvacinfo->max_vacuums = MaxBackends;
-	}
-	else
-		Assert(found);
+	btvacinfo->num_vacuums = 0;
+	btvacinfo->max_vacuums = MaxBackends;
 }
 
 bytea *
diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c
index ab1cbd67bac..836928180a9 100644
--- a/src/backend/access/transam/twophase.c
+++ b/src/backend/access/transam/twophase.c
@@ -102,6 +102,7 @@
 #include "storage/predicate.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
+#include "storage/subsystems.h"
 #include "utils/builtins.h"
 #include "utils/injection_point.h"
 #include "utils/memutils.h"
@@ -187,8 +188,16 @@ typedef struct TwoPhaseStateData
 	GlobalTransaction prepXacts[FLEXIBLE_ARRAY_MEMBER];
 } TwoPhaseStateData;
 
+static void TwoPhaseShmemRequest(void *arg);
+static void TwoPhaseShmemInit(void *arg);
+
 static TwoPhaseStateData *TwoPhaseState;
 
+const ShmemCallbacks TwoPhaseShmemCallbacks = {
+	.request_fn = TwoPhaseShmemRequest,
+	.init_fn = TwoPhaseShmemInit,
+};
+
 /*
  * Global transaction entry currently locked by us, if any.  Note that any
  * access to the entry pointed to by this variable must be protected by
@@ -234,10 +243,10 @@ static void RemoveTwoPhaseFile(FullTransactionId fxid, bool giveWarning);
 static void RecreateTwoPhaseFile(FullTransactionId fxid, void *content, int len);
 
 /*
- * Initialization of shared memory
+ * Register shared memory for two-phase state.
  */
-Size
-TwoPhaseShmemSize(void)
+static void
+TwoPhaseShmemRequest(void *arg)
 {
 	Size		size;
 
@@ -248,46 +257,40 @@ TwoPhaseShmemSize(void)
 	size = MAXALIGN(size);
 	size = add_size(size, mul_size(max_prepared_xacts,
 								   sizeof(GlobalTransactionData)));
-
-	return size;
+	ShmemRequestStruct(.name = "Prepared Transaction Table",
+					   .size = size,
+					   .ptr = (void **) &TwoPhaseState,
+		);
 }
 
-void
-TwoPhaseShmemInit(void)
+/*
+ * Initialize shared memory for two-phase state.
+ */
+static void
+TwoPhaseShmemInit(void *arg)
 {
-	bool		found;
-
-	TwoPhaseState = ShmemInitStruct("Prepared Transaction Table",
-									TwoPhaseShmemSize(),
-									&found);
-	if (!IsUnderPostmaster)
-	{
-		GlobalTransaction gxacts;
-		int			i;
+	GlobalTransaction gxacts;
+	int			i;
 
-		Assert(!found);
-		TwoPhaseState->freeGXacts = NULL;
-		TwoPhaseState->numPrepXacts = 0;
+	TwoPhaseState->freeGXacts = NULL;
+	TwoPhaseState->numPrepXacts = 0;
 
-		/*
-		 * Initialize the linked list of free GlobalTransactionData structs
-		 */
-		gxacts = (GlobalTransaction)
-			((char *) TwoPhaseState +
-			 MAXALIGN(offsetof(TwoPhaseStateData, prepXacts) +
-					  sizeof(GlobalTransaction) * max_prepared_xacts));
-		for (i = 0; i < max_prepared_xacts; i++)
-		{
-			/* insert into linked list */
-			gxacts[i].next = TwoPhaseState->freeGXacts;
-			TwoPhaseState->freeGXacts = &gxacts[i];
+	/*
+	 * Initialize the linked list of free GlobalTransactionData structs
+	 */
+	gxacts = (GlobalTransaction)
+		((char *) TwoPhaseState +
+		 MAXALIGN(offsetof(TwoPhaseStateData, prepXacts) +
+				  sizeof(GlobalTransaction) * max_prepared_xacts));
+	for (i = 0; i < max_prepared_xacts; i++)
+	{
+		/* insert into linked list */
+		gxacts[i].next = TwoPhaseState->freeGXacts;
+		TwoPhaseState->freeGXacts = &gxacts[i];
 
-			/* associate it with a PGPROC assigned by ProcGlobalShmemInit */
-			gxacts[i].pgprocno = GetNumberFromPGProc(&PreparedXactProcs[i]);
-		}
+		/* associate it with a PGPROC assigned by ProcGlobalShmemInit */
+		gxacts[i].pgprocno = GetNumberFromPGProc(&PreparedXactProcs[i]);
 	}
-	else
-		Assert(found);
 }
 
 /*
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 9e8999bbb61..bbc565509b0 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -96,6 +96,7 @@
 #include "storage/procsignal.h"
 #include "storage/reinit.h"
 #include "storage/spin.h"
+#include "storage/subsystems.h"
 #include "storage/sync.h"
 #include "utils/guc_hooks.h"
 #include "utils/guc_tables.h"
@@ -571,6 +572,16 @@ typedef enum
 	WALINSERT_SPECIAL_CHECKPOINT
 } WalInsertClass;
 
+static void XLOGShmemRequest(void *arg);
+static void XLOGShmemInit(void *arg);
+static void XLOGShmemAttach(void *arg);
+
+const ShmemCallbacks XLOGShmemCallbacks = {
+	.request_fn = XLOGShmemRequest,
+	.init_fn = XLOGShmemInit,
+	.attach_fn = XLOGShmemAttach,
+};
+
 static XLogCtlData *XLogCtl = NULL;
 
 /* a private copy of XLogCtl->Insert.WALInsertLocks, for convenience */
@@ -579,6 +590,7 @@ static WALInsertLockPadded *WALInsertLocks = NULL;
 /*
  * We maintain an image of pg_control in shared memory.
  */
+static ControlFileData *LocalControlFile = NULL;
 static ControlFileData *ControlFile = NULL;
 
 /*
@@ -5257,7 +5269,8 @@ void
 LocalProcessControlFile(bool reset)
 {
 	Assert(reset || ControlFile == NULL);
-	ControlFile = palloc_object(ControlFileData);
+	LocalControlFile = palloc_object(ControlFileData);
+	ControlFile = LocalControlFile;
 	ReadControlFile();
 	SetLocalDataChecksumState(ControlFile->data_checksum_version);
 }
@@ -5274,10 +5287,10 @@ GetActiveWalLevelOnStandby(void)
 }
 
 /*
- * Initialization of shared memory for XLOG
+ * Register shared memory for XLOG.
  */
-Size
-XLOGShmemSize(void)
+static void
+XLOGShmemRequest(void *arg)
 {
 	Size		size;
 
@@ -5317,23 +5330,24 @@ XLOGShmemSize(void)
 	/* and the buffers themselves */
 	size = add_size(size, mul_size(XLOG_BLCKSZ, XLOGbuffers));
 
-	/*
-	 * Note: we don't count ControlFileData, it comes out of the "slop factor"
-	 * added by CreateSharedMemoryAndSemaphores.  This lets us use this
-	 * routine again below to compute the actual allocation size.
-	 */
-
-	return size;
+	ShmemRequestStruct(.name = "XLOG Ctl",
+					   .size = size,
+					   .ptr = (void **) &XLogCtl,
+		);
+	ShmemRequestStruct(.name = "Control File",
+					   .size = sizeof(ControlFileData),
+					   .ptr = (void **) &ControlFile,
+		);
 }
 
-void
-XLOGShmemInit(void)
+/*
+ * XLOGShmemInit - initialize the XLogCtl shared memory area.
+ */
+static void
+XLOGShmemInit(void *arg)
 {
-	bool		foundCFile,
-				foundXLog;
 	char	   *allocptr;
 	int			i;
-	ControlFileData *localControlFile;
 
 #ifdef WAL_DEBUG
 
@@ -5351,36 +5365,17 @@ XLOGShmemInit(void)
 	}
 #endif
 
-
-	XLogCtl = (XLogCtlData *)
-		ShmemInitStruct("XLOG Ctl", XLOGShmemSize(), &foundXLog);
-
-	localControlFile = ControlFile;
-	ControlFile = (ControlFileData *)
-		ShmemInitStruct("Control File", sizeof(ControlFileData), &foundCFile);
-
-	if (foundCFile || foundXLog)
-	{
-		/* both should be present or neither */
-		Assert(foundCFile && foundXLog);
-
-		/* Initialize local copy of WALInsertLocks */
-		WALInsertLocks = XLogCtl->Insert.WALInsertLocks;
-
-		if (localControlFile)
-			pfree(localControlFile);
-		return;
-	}
 	memset(XLogCtl, 0, sizeof(XLogCtlData));
 
 	/*
 	 * Already have read control file locally, unless in bootstrap mode. Move
 	 * contents into shared memory.
 	 */
-	if (localControlFile)
+	if (LocalControlFile)
 	{
-		memcpy(ControlFile, localControlFile, sizeof(ControlFileData));
-		pfree(localControlFile);
+		memcpy(ControlFile, LocalControlFile, sizeof(ControlFileData));
+		pfree(LocalControlFile);
+		LocalControlFile = NULL;
 	}
 
 	/*
@@ -5442,6 +5437,15 @@ XLOGShmemInit(void)
 	pg_atomic_init_u64(&XLogCtl->unloggedLSN, InvalidXLogRecPtr);
 }
 
+/*
+ * XLOGShmemAttach - set up WALInsertLocks pointer after attaching.
+ */
+static void
+XLOGShmemAttach(void *arg)
+{
+	WALInsertLocks = XLogCtl->Insert.WALInsertLocks;
+}
+
 /*
  * This func must be called ONCE on system install.  It creates pg_control
  * and the initial XLOG segment.
diff --git a/src/backend/access/transam/xlogprefetcher.c b/src/backend/access/transam/xlogprefetcher.c
index c235eca7c51..83a3f97a57c 100644
--- a/src/backend/access/transam/xlogprefetcher.c
+++ b/src/backend/access/transam/xlogprefetcher.c
@@ -39,6 +39,7 @@
 #include "storage/fd.h"
 #include "storage/shmem.h"
 #include "storage/smgr.h"
+#include "storage/subsystems.h"
 #include "utils/fmgrprotos.h"
 #include "utils/guc_hooks.h"
 #include "utils/hsearch.h"
@@ -200,6 +201,14 @@ static LsnReadQueueNextStatus XLogPrefetcherNextBlock(uintptr_t pgsr_private,
 
 static XLogPrefetchStats *SharedStats;
 
+static void XLogPrefetchShmemRequest(void *arg);
+static void XLogPrefetchShmemInit(void *arg);
+
+const ShmemCallbacks XLogPrefetchShmemCallbacks = {
+	.request_fn = XLogPrefetchShmemRequest,
+	.init_fn = XLogPrefetchShmemInit,
+};
+
 static inline LsnReadQueue *
 lrq_alloc(uint32 max_distance,
 		  uint32 max_inflight,
@@ -292,10 +301,25 @@ lrq_complete_lsn(LsnReadQueue *lrq, XLogRecPtr lsn)
 		lrq_prefetch(lrq);
 }
 
-size_t
-XLogPrefetchShmemSize(void)
+static void
+XLogPrefetchShmemRequest(void *arg)
+{
+	ShmemRequestStruct(.name = "XLogPrefetchStats",
+					   .size = sizeof(XLogPrefetchStats),
+					   .ptr = (void **) &SharedStats,
+		);
+}
+
+static void
+XLogPrefetchShmemInit(void *arg)
 {
-	return sizeof(XLogPrefetchStats);
+	pg_atomic_init_u64(&SharedStats->reset_time, GetCurrentTimestamp());
+	pg_atomic_init_u64(&SharedStats->prefetch, 0);
+	pg_atomic_init_u64(&SharedStats->hit, 0);
+	pg_atomic_init_u64(&SharedStats->skip_init, 0);
+	pg_atomic_init_u64(&SharedStats->skip_new, 0);
+	pg_atomic_init_u64(&SharedStats->skip_fpw, 0);
+	pg_atomic_init_u64(&SharedStats->skip_rep, 0);
 }
 
 /*
@@ -313,27 +337,6 @@ XLogPrefetchResetStats(void)
 	pg_atomic_write_u64(&SharedStats->skip_rep, 0);
 }
 
-void
-XLogPrefetchShmemInit(void)
-{
-	bool		found;
-
-	SharedStats = (XLogPrefetchStats *)
-		ShmemInitStruct("XLogPrefetchStats",
-						sizeof(XLogPrefetchStats),
-						&found);
-
-	if (!found)
-	{
-		pg_atomic_init_u64(&SharedStats->reset_time, GetCurrentTimestamp());
-		pg_atomic_init_u64(&SharedStats->prefetch, 0);
-		pg_atomic_init_u64(&SharedStats->hit, 0);
-		pg_atomic_init_u64(&SharedStats->skip_init, 0);
-		pg_atomic_init_u64(&SharedStats->skip_new, 0);
-		pg_atomic_init_u64(&SharedStats->skip_fpw, 0);
-		pg_atomic_init_u64(&SharedStats->skip_rep, 0);
-	}
-}
 
 /*
  * Called when any GUC is changed that affects prefetching.
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index fd1c36d061d..c236e2b7969 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -58,6 +58,7 @@
 #include "storage/pmsignal.h"
 #include "storage/procarray.h"
 #include "storage/spin.h"
+#include "storage/subsystems.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
 #include "utils/guc_hooks.h"
@@ -307,6 +308,14 @@ static char *primary_image_masked = NULL;
 
 XLogRecoveryCtlData *XLogRecoveryCtl = NULL;
 
+static void XLogRecoveryShmemRequest(void *arg);
+static void XLogRecoveryShmemInit(void *arg);
+
+const ShmemCallbacks XLogRecoveryShmemCallbacks = {
+	.request_fn = XLogRecoveryShmemRequest,
+	.init_fn = XLogRecoveryShmemInit,
+};
+
 /*
  * abortedRecPtr is the start pointer of a broken record at end of WAL when
  * recovery completes; missingContrecPtr is the location of the first
@@ -385,28 +394,20 @@ static void SetCurrentChunkStartTime(TimestampTz xtime);
 static void SetLatestXTime(TimestampTz xtime);
 
 /*
- * Initialization of shared memory for WAL recovery
+ * Register shared memory for WAL recovery
  */
-Size
-XLogRecoveryShmemSize(void)
+static void
+XLogRecoveryShmemRequest(void *arg)
 {
-	Size		size;
-
-	/* XLogRecoveryCtl */
-	size = sizeof(XLogRecoveryCtlData);
-
-	return size;
+	ShmemRequestStruct(.name = "XLOG Recovery Ctl",
+					   .size = sizeof(XLogRecoveryCtlData),
+					   .ptr = (void **) &XLogRecoveryCtl,
+		);
 }
 
-void
-XLogRecoveryShmemInit(void)
+static void
+XLogRecoveryShmemInit(void *arg)
 {
-	bool		found;
-
-	XLogRecoveryCtl = (XLogRecoveryCtlData *)
-		ShmemInitStruct("XLOG Recovery Ctl", XLogRecoveryShmemSize(), &found);
-	if (found)
-		return;
 	memset(XLogRecoveryCtl, 0, sizeof(XLogRecoveryCtlData));
 
 	SpinLockInit(&XLogRecoveryCtl->info_lck);
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
index bf4630677b4..2e31c0d67d7 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -57,6 +57,7 @@
 #include "storage/latch.h"
 #include "storage/proc.h"
 #include "storage/shmem.h"
+#include "storage/subsystems.h"
 #include "utils/fmgrprotos.h"
 #include "utils/pg_lsn.h"
 #include "utils/snapmgr.h"
@@ -68,6 +69,14 @@ static int	waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b,
 
 struct WaitLSNState *waitLSNState = NULL;
 
+static void WaitLSNShmemRequest(void *arg);
+static void WaitLSNShmemInit(void *arg);
+
+const ShmemCallbacks WaitLSNShmemCallbacks = {
+	.request_fn = WaitLSNShmemRequest,
+	.init_fn = WaitLSNShmemInit,
+};
+
 /*
  * Wait event for each WaitLSNType, used with WaitLatch() to report
  * the wait in pg_stat_activity.
@@ -109,41 +118,34 @@ GetCurrentLSNForWaitType(WaitLSNType lsnType)
 	pg_unreachable();
 }
 
-/* Report the amount of shared memory space needed for WaitLSNState. */
-Size
-WaitLSNShmemSize(void)
+/* Register the shared memory space needed for WaitLSNState. */
+static void
+WaitLSNShmemRequest(void *arg)
 {
 	Size		size;
 
 	size = offsetof(WaitLSNState, procInfos);
 	size = add_size(size, mul_size(MaxBackends + NUM_AUXILIARY_PROCS, sizeof(WaitLSNProcInfo)));
-	return size;
+	ShmemRequestStruct(.name = "WaitLSNState",
+					   .size = size,
+					   .ptr = (void **) &waitLSNState,
+		);
 }
 
 /* Initialize the WaitLSNState in the shared memory. */
-void
-WaitLSNShmemInit(void)
+static void
+WaitLSNShmemInit(void *arg)
 {
-	bool		found;
-
-	waitLSNState = (WaitLSNState *) ShmemInitStruct("WaitLSNState",
-													WaitLSNShmemSize(),
-													&found);
-	if (!found)
+	/* Initialize heaps and tracking */
+	for (int i = 0; i < WAIT_LSN_TYPE_COUNT; i++)
 	{
-		int			i;
-
-		/* Initialize heaps and tracking */
-		for (i = 0; i < WAIT_LSN_TYPE_COUNT; i++)
-		{
-			pg_atomic_init_u64(&waitLSNState->minWaitedLSN[i], PG_UINT64_MAX);
-			pairingheap_initialize(&waitLSNState->waitersHeap[i], waitlsn_cmp, NULL);
-		}
-
-		/* Initialize process info array */
-		memset(&waitLSNState->procInfos, 0,
-			   (MaxBackends + NUM_AUXILIARY_PROCS) * sizeof(WaitLSNProcInfo));
+		pg_atomic_init_u64(&waitLSNState->minWaitedLSN[i], PG_UINT64_MAX);
+		pairingheap_initialize(&waitLSNState->waitersHeap[i], waitlsn_cmp, NULL);
 	}
+
+	/* Initialize process info array */
+	memset(&waitLSNState->procInfos, 0,
+		   (MaxBackends + NUM_AUXILIARY_PROCS) * sizeof(WaitLSNProcInfo));
 }
 
 /*
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 8400e6722cc..250c43b85e5 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -98,6 +98,7 @@
 #include "storage/proc.h"
 #include "storage/procsignal.h"
 #include "storage/smgr.h"
+#include "storage/subsystems.h"
 #include "tcop/tcopprot.h"
 #include "utils/fmgroids.h"
 #include "utils/fmgrprotos.h"
@@ -309,6 +310,14 @@ typedef struct
 
 static AutoVacuumShmemStruct *AutoVacuumShmem;
 
+static void AutoVacuumShmemRequest(void *arg);
+static void AutoVacuumShmemInit(void *arg);
+
+const ShmemCallbacks AutoVacuumShmemCallbacks = {
+	.request_fn = AutoVacuumShmemRequest,
+	.init_fn = AutoVacuumShmemInit,
+};
+
 /*
  * the database list (of avl_dbase elements) in the launcher, and the context
  * that contains it
@@ -3545,11 +3554,11 @@ autovac_init(void)
 }
 
 /*
- * AutoVacuumShmemSize
- *		Compute space needed for autovacuum-related shared memory
+ * AutoVacuumShmemRequest
+ *		Register shared memory space needed for autovacuum
  */
-Size
-AutoVacuumShmemSize(void)
+static void
+AutoVacuumShmemRequest(void *arg)
 {
 	Size		size;
 
@@ -3560,53 +3569,41 @@ AutoVacuumShmemSize(void)
 	size = MAXALIGN(size);
 	size = add_size(size, mul_size(autovacuum_worker_slots,
 								   sizeof(WorkerInfoData)));
-	return size;
+
+	ShmemRequestStruct(.name = "AutoVacuum Data",
+					   .size = size,
+					   .ptr = (void **) &AutoVacuumShmem,
+		);
 }
 
 /*
  * AutoVacuumShmemInit
- *		Allocate and initialize autovacuum-related shared memory
+ *		Initialize autovacuum-related shared memory
  */
-void
-AutoVacuumShmemInit(void)
+static void
+AutoVacuumShmemInit(void *arg)
 {
-	bool		found;
-
-	AutoVacuumShmem = (AutoVacuumShmemStruct *)
-		ShmemInitStruct("AutoVacuum Data",
-						AutoVacuumShmemSize(),
-						&found);
-
-	if (!IsUnderPostmaster)
-	{
-		WorkerInfo	worker;
-		int			i;
+	WorkerInfo	worker;
 
-		Assert(!found);
-
-		AutoVacuumShmem->av_launcherpid = 0;
-		dclist_init(&AutoVacuumShmem->av_freeWorkers);
-		dlist_init(&AutoVacuumShmem->av_runningWorkers);
-		AutoVacuumShmem->av_startingWorker = NULL;
-		memset(AutoVacuumShmem->av_workItems, 0,
-			   sizeof(AutoVacuumWorkItem) * NUM_WORKITEMS);
-
-		worker = (WorkerInfo) ((char *) AutoVacuumShmem +
-							   MAXALIGN(sizeof(AutoVacuumShmemStruct)));
-
-		/* initialize the WorkerInfo free list */
-		for (i = 0; i < autovacuum_worker_slots; i++)
-		{
-			dclist_push_head(&AutoVacuumShmem->av_freeWorkers,
-							 &worker[i].wi_links);
-			pg_atomic_init_flag(&worker[i].wi_dobalance);
-		}
+	AutoVacuumShmem->av_launcherpid = 0;
+	dclist_init(&AutoVacuumShmem->av_freeWorkers);
+	dlist_init(&AutoVacuumShmem->av_runningWorkers);
+	AutoVacuumShmem->av_startingWorker = NULL;
+	memset(AutoVacuumShmem->av_workItems, 0,
+		   sizeof(AutoVacuumWorkItem) * NUM_WORKITEMS);
 
-		pg_atomic_init_u32(&AutoVacuumShmem->av_nworkersForBalance, 0);
+	worker = (WorkerInfo) ((char *) AutoVacuumShmem +
+						   MAXALIGN(sizeof(AutoVacuumShmemStruct)));
 
+	/* initialize the WorkerInfo free list */
+	for (int i = 0; i < autovacuum_worker_slots; i++)
+	{
+		dclist_push_head(&AutoVacuumShmem->av_freeWorkers,
+						 &worker[i].wi_links);
+		pg_atomic_init_flag(&worker[i].wi_dobalance);
 	}
-	else
-		Assert(found);
+
+	pg_atomic_init_u32(&AutoVacuumShmem->av_nworkersForBalance, 0);
 }
 
 /*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index 536aff7ca05..0992b9b6353 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -30,6 +30,7 @@
 #include "storage/procarray.h"
 #include "storage/procsignal.h"
 #include "storage/shmem.h"
+#include "storage/subsystems.h"
 #include "tcop/tcopprot.h"
 #include "utils/ascii.h"
 #include "utils/memutils.h"
@@ -110,6 +111,14 @@ struct BackgroundWorkerHandle
 
 static BackgroundWorkerArray *BackgroundWorkerData;
 
+static void BackgroundWorkerShmemRequest(void *arg);
+static void BackgroundWorkerShmemInit(void *arg);
+
+const ShmemCallbacks BackgroundWorkerShmemCallbacks = {
+	.request_fn = BackgroundWorkerShmemRequest,
+	.init_fn = BackgroundWorkerShmemInit,
+};
+
 /*
  * List of internal background worker entry points.  We need this for
  * reasons explained in LookupBackgroundWorkerFunction(), below.
@@ -160,10 +169,10 @@ static bgworker_main_type LookupBackgroundWorkerFunction(const char *libraryname
 
 
 /*
- * Calculate shared memory needed.
+ * Register shared memory needed for background workers.
  */
-Size
-BackgroundWorkerShmemSize(void)
+static void
+BackgroundWorkerShmemRequest(void *arg)
 {
 	Size		size;
 
@@ -171,66 +180,58 @@ BackgroundWorkerShmemSize(void)
 	size = offsetof(BackgroundWorkerArray, slot);
 	size = add_size(size, mul_size(max_worker_processes,
 								   sizeof(BackgroundWorkerSlot)));
-
-	return size;
+	ShmemRequestStruct(.name = "Background Worker Data",
+					   .size = size,
+					   .ptr = (void **) &BackgroundWorkerData,
+		);
 }
 
 /*
- * Initialize shared memory.
+ * Initialize shared memory for background workers.
  */
-void
-BackgroundWorkerShmemInit(void)
+static void
+BackgroundWorkerShmemInit(void *arg)
 {
-	bool		found;
-
-	BackgroundWorkerData = ShmemInitStruct("Background Worker Data",
-										   BackgroundWorkerShmemSize(),
-										   &found);
-	if (!IsUnderPostmaster)
-	{
-		dlist_iter	iter;
-		int			slotno = 0;
+	dlist_iter	iter;
+	int			slotno = 0;
 
-		BackgroundWorkerData->total_slots = max_worker_processes;
-		BackgroundWorkerData->parallel_register_count = 0;
-		BackgroundWorkerData->parallel_terminate_count = 0;
+	BackgroundWorkerData->total_slots = max_worker_processes;
+	BackgroundWorkerData->parallel_register_count = 0;
+	BackgroundWorkerData->parallel_terminate_count = 0;
 
-		/*
-		 * Copy contents of worker list into shared memory.  Record the shared
-		 * memory slot assigned to each worker.  This ensures a 1-to-1
-		 * correspondence between the postmaster's private list and the array
-		 * in shared memory.
-		 */
-		dlist_foreach(iter, &BackgroundWorkerList)
-		{
-			BackgroundWorkerSlot *slot = &BackgroundWorkerData->slot[slotno];
-			RegisteredBgWorker *rw;
+	/*
+	 * Copy contents of worker list into shared memory.  Record the shared
+	 * memory slot assigned to each worker.  This ensures a 1-to-1
+	 * correspondence between the postmaster's private list and the array in
+	 * shared memory.
+	 */
+	dlist_foreach(iter, &BackgroundWorkerList)
+	{
+		BackgroundWorkerSlot *slot = &BackgroundWorkerData->slot[slotno];
+		RegisteredBgWorker *rw;
 
-			rw = dlist_container(RegisteredBgWorker, rw_lnode, iter.cur);
-			Assert(slotno < max_worker_processes);
-			slot->in_use = true;
-			slot->terminate = false;
-			slot->pid = InvalidPid;
-			slot->generation = 0;
-			rw->rw_shmem_slot = slotno;
-			rw->rw_worker.bgw_notify_pid = 0;	/* might be reinit after crash */
-			memcpy(&slot->worker, &rw->rw_worker, sizeof(BackgroundWorker));
-			++slotno;
-		}
+		rw = dlist_container(RegisteredBgWorker, rw_lnode, iter.cur);
+		Assert(slotno < max_worker_processes);
+		slot->in_use = true;
+		slot->terminate = false;
+		slot->pid = InvalidPid;
+		slot->generation = 0;
+		rw->rw_shmem_slot = slotno;
+		rw->rw_worker.bgw_notify_pid = 0;	/* might be reinit after crash */
+		memcpy(&slot->worker, &rw->rw_worker, sizeof(BackgroundWorker));
+		++slotno;
+	}
 
-		/*
-		 * Mark any remaining slots as not in use.
-		 */
-		while (slotno < max_worker_processes)
-		{
-			BackgroundWorkerSlot *slot = &BackgroundWorkerData->slot[slotno];
+	/*
+	 * Mark any remaining slots as not in use.
+	 */
+	while (slotno < max_worker_processes)
+	{
+		BackgroundWorkerSlot *slot = &BackgroundWorkerData->slot[slotno];
 
-			slot->in_use = false;
-			++slotno;
-		}
+		slot->in_use = false;
+		++slotno;
 	}
-	else
-		Assert(found);
 }
 
 /*
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index 3c982c6ffac..6b424ee610f 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -63,6 +63,7 @@
 #include "storage/shmem.h"
 #include "storage/smgr.h"
 #include "storage/spin.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/guc.h"
 #include "utils/memutils.h"
@@ -143,6 +144,14 @@ typedef struct
 
 static CheckpointerShmemStruct *CheckpointerShmem;
 
+static void CheckpointerShmemRequest(void *arg);
+static void CheckpointerShmemInit(void *arg);
+
+const ShmemCallbacks CheckpointerShmemCallbacks = {
+	.request_fn = CheckpointerShmemRequest,
+	.init_fn = CheckpointerShmemInit,
+};
+
 /* interval for calling AbsorbSyncRequests in CheckpointWriteDelay */
 #define WRITES_PER_ABSORB		1000
 
@@ -950,11 +959,11 @@ ReqShutdownXLOG(SIGNAL_ARGS)
  */
 
 /*
- * CheckpointerShmemSize
- *		Compute space needed for checkpointer-related shared memory
+ * CheckpointerShmemRequest
+ *		Register shared memory space needed for checkpointer
  */
-Size
-CheckpointerShmemSize(void)
+static void
+CheckpointerShmemRequest(void *arg)
 {
 	Size		size;
 
@@ -967,39 +976,24 @@ CheckpointerShmemSize(void)
 	size = add_size(size, mul_size(Min(NBuffers,
 									   MAX_CHECKPOINT_REQUESTS),
 								   sizeof(CheckpointerRequest)));
-
-	return size;
+	ShmemRequestStruct(.name = "Checkpointer Data",
+					   .size = size,
+					   .ptr = (void **) &CheckpointerShmem,
+		);
 }
 
 /*
  * CheckpointerShmemInit
- *		Allocate and initialize checkpointer-related shared memory
+ *		Initialize checkpointer-related shared memory
  */
-void
-CheckpointerShmemInit(void)
+static void
+CheckpointerShmemInit(void *arg)
 {
-	Size		size = CheckpointerShmemSize();
-	bool		found;
-
-	CheckpointerShmem = (CheckpointerShmemStruct *)
-		ShmemInitStruct("Checkpointer Data",
-						size,
-						&found);
-
-	if (!found)
-	{
-		/*
-		 * First time through, so initialize.  Note that we zero the whole
-		 * requests array; this is so that CompactCheckpointerRequestQueue can
-		 * assume that any pad bytes in the request structs are zeroes.
-		 */
-		MemSet(CheckpointerShmem, 0, size);
-		SpinLockInit(&CheckpointerShmem->ckpt_lck);
-		CheckpointerShmem->max_requests = Min(NBuffers, MAX_CHECKPOINT_REQUESTS);
-		CheckpointerShmem->head = CheckpointerShmem->tail = 0;
-		ConditionVariableInit(&CheckpointerShmem->start_cv);
-		ConditionVariableInit(&CheckpointerShmem->done_cv);
-	}
+	SpinLockInit(&CheckpointerShmem->ckpt_lck);
+	CheckpointerShmem->max_requests = Min(NBuffers, MAX_CHECKPOINT_REQUESTS);
+	CheckpointerShmem->head = CheckpointerShmem->tail = 0;
+	ConditionVariableInit(&CheckpointerShmem->start_cv);
+	ConditionVariableInit(&CheckpointerShmem->done_cv);
 }
 
 /*
diff --git a/src/backend/postmaster/datachecksum_state.c b/src/backend/postmaster/datachecksum_state.c
index 76004bcedc6..eb7b01d0993 100644
--- a/src/backend/postmaster/datachecksum_state.c
+++ b/src/backend/postmaster/datachecksum_state.c
@@ -211,6 +211,7 @@
 #include "storage/lwlock.h"
 #include "storage/procarray.h"
 #include "storage/smgr.h"
+#include "storage/subsystems.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
@@ -346,6 +347,7 @@ static volatile sig_atomic_t launcher_running = false;
 static DataChecksumsWorkerOperation operation;
 
 /* Prototypes */
+static void DataChecksumsShmemRequest(void *arg);
 static bool DatabaseExists(Oid dboid);
 static List *BuildDatabaseList(void);
 static List *BuildRelationList(bool temp_relations, bool include_shared);
@@ -356,6 +358,10 @@ static bool ProcessSingleRelationFork(Relation reln, ForkNumber forkNum, BufferA
 static void launcher_cancel_handler(SIGNAL_ARGS);
 static void WaitForAllTransactionsToFinish(void);
 
+const ShmemCallbacks DataChecksumsShmemCallbacks = {
+	.request_fn = DataChecksumsShmemRequest,
+};
+
 /*****************************************************************************
  * Functionality for manipulating the data checksum state in the cluster
  */
@@ -1236,35 +1242,16 @@ ProcessAllDatabases(void)
 }
 
 /*
- * DataChecksumStateSize
- *		Compute required space for datachecksumsworker-related shared memory
- */
-Size
-DataChecksumsShmemSize(void)
-{
-	Size		size;
-
-	size = sizeof(DataChecksumsStateStruct);
-	size = MAXALIGN(size);
-
-	return size;
-}
-
-/*
- * DataChecksumStateInit
- *		Allocate and initialize datachecksumsworker-related shared memory
+ * DataChecksumShmemRequest
+ *		Request datachecksumsworker-related shared memory
  */
-void
-DataChecksumsShmemInit(void)
+static void
+DataChecksumsShmemRequest(void *arg)
 {
-	bool		found;
-
-	DataChecksumState = (DataChecksumsStateStruct *)
-		ShmemInitStruct("DataChecksumsWorker Data",
-						DataChecksumsShmemSize(),
-						&found);
-	if (!found)
-		MemSet(DataChecksumState, 0, DataChecksumsShmemSize());
+	ShmemRequestStruct(.name = "DataChecksumsWorker Data",
+					   .size = sizeof(DataChecksumsStateStruct),
+					   .ptr = (void **) &DataChecksumState,
+		);
 }
 
 /*
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index fa4bdfe9ab9..0a1a1149d78 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -48,6 +48,7 @@
 #include "storage/proc.h"
 #include "storage/procsignal.h"
 #include "storage/shmem.h"
+#include "storage/subsystems.h"
 #include "utils/guc.h"
 #include "utils/memutils.h"
 #include "utils/ps_status.h"
@@ -154,33 +155,31 @@ static int	ready_file_comparator(Datum a, Datum b, void *arg);
 static void LoadArchiveLibrary(void);
 static void pgarch_call_module_shutdown_cb(int code, Datum arg);
 
-/* Report shared memory space needed by PgArchShmemInit */
-Size
-PgArchShmemSize(void)
-{
-	Size		size = 0;
+static void PgArchShmemRequest(void *arg);
+static void PgArchShmemInit(void *arg);
 
-	size = add_size(size, sizeof(PgArchData));
+const ShmemCallbacks PgArchShmemCallbacks = {
+	.request_fn = PgArchShmemRequest,
+	.init_fn = PgArchShmemInit,
+};
 
-	return size;
+/* Register shared memory space needed by the archiver */
+static void
+PgArchShmemRequest(void *arg)
+{
+	ShmemRequestStruct(.name = "Archiver Data",
+					   .size = sizeof(PgArchData),
+					   .ptr = (void **) &PgArch,
+		);
 }
 
-/* Allocate and initialize archiver-related shared memory */
-void
-PgArchShmemInit(void)
+/* Initialize archiver-related shared memory */
+static void
+PgArchShmemInit(void *arg)
 {
-	bool		found;
-
-	PgArch = (PgArchData *)
-		ShmemInitStruct("Archiver Data", PgArchShmemSize(), &found);
-
-	if (!found)
-	{
-		/* First time through, so initialize */
-		MemSet(PgArch, 0, PgArchShmemSize());
-		PgArch->pgprocno = INVALID_PROC_NUMBER;
-		pg_atomic_init_u32(&PgArch->force_dir_scan, 0);
-	}
+	MemSet(PgArch, 0, sizeof(PgArchData));
+	PgArch->pgprocno = INVALID_PROC_NUMBER;
+	pg_atomic_init_u32(&PgArch->force_dir_scan, 0);
 }
 
 /*
diff --git a/src/backend/postmaster/walsummarizer.c b/src/backend/postmaster/walsummarizer.c
index a37b3018abf..20960f5b633 100644
--- a/src/backend/postmaster/walsummarizer.c
+++ b/src/backend/postmaster/walsummarizer.c
@@ -47,6 +47,7 @@
 #include "storage/proc.h"
 #include "storage/procsignal.h"
 #include "storage/shmem.h"
+#include "storage/subsystems.h"
 #include "utils/guc.h"
 #include "utils/memutils.h"
 #include "utils/wait_event.h"
@@ -109,6 +110,14 @@ typedef struct
 /* Pointer to shared memory state. */
 static WalSummarizerData *WalSummarizerCtl;
 
+static void WalSummarizerShmemRequest(void *arg);
+static void WalSummarizerShmemInit(void *arg);
+
+const ShmemCallbacks WalSummarizerShmemCallbacks = {
+	.request_fn = WalSummarizerShmemRequest,
+	.init_fn = WalSummarizerShmemInit,
+};
+
 /*
  * When we reach end of WAL and need to read more, we sleep for a number of
  * milliseconds that is an integer multiple of MS_PER_SLEEP_QUANTUM. This is
@@ -168,43 +177,34 @@ static void summarizer_wait_for_wal(void);
 static void MaybeRemoveOldWalSummaries(void);
 
 /*
- * Amount of shared memory required for this module.
+ * Register shared memory space needed by this module.
  */
-Size
-WalSummarizerShmemSize(void)
+static void
+WalSummarizerShmemRequest(void *arg)
 {
-	return sizeof(WalSummarizerData);
+	ShmemRequestStruct(.name = "Wal Summarizer Ctl",
+					   .size = sizeof(WalSummarizerData),
+					   .ptr = (void **) &WalSummarizerCtl,
+		);
 }
 
 /*
- * Create or attach to shared memory segment for this module.
+ * Initialize shared memory for this module.
  */
-void
-WalSummarizerShmemInit(void)
+static void
+WalSummarizerShmemInit(void *arg)
 {
-	bool		found;
-
-	WalSummarizerCtl = (WalSummarizerData *)
-		ShmemInitStruct("Wal Summarizer Ctl", WalSummarizerShmemSize(),
-						&found);
-
-	if (!found)
-	{
-		/*
-		 * First time through, so initialize.
-		 *
-		 * We're just filling in dummy values here -- the real initialization
-		 * will happen when GetOldestUnsummarizedLSN() is called for the first
-		 * time.
-		 */
-		WalSummarizerCtl->initialized = false;
-		WalSummarizerCtl->summarized_tli = 0;
-		WalSummarizerCtl->summarized_lsn = InvalidXLogRecPtr;
-		WalSummarizerCtl->lsn_is_exact = false;
-		WalSummarizerCtl->summarizer_pgprocno = INVALID_PROC_NUMBER;
-		WalSummarizerCtl->pending_lsn = InvalidXLogRecPtr;
-		ConditionVariableInit(&WalSummarizerCtl->summary_file_cv);
-	}
+	/*
+	 * We're just filling in dummy values here -- the real initialization will
+	 * happen when GetOldestUnsummarizedLSN() is called for the first time.
+	 */
+	WalSummarizerCtl->initialized = false;
+	WalSummarizerCtl->summarized_tli = 0;
+	WalSummarizerCtl->summarized_lsn = InvalidXLogRecPtr;
+	WalSummarizerCtl->lsn_is_exact = false;
+	WalSummarizerCtl->summarizer_pgprocno = INVALID_PROC_NUMBER;
+	WalSummarizerCtl->pending_lsn = InvalidXLogRecPtr;
+	ConditionVariableInit(&WalSummarizerCtl->summary_file_cv);
 }
 
 /*
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..9e75a3e04ee 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -38,6 +38,7 @@
 #include "storage/ipc.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
+#include "storage/subsystems.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
 #include "utils/memutils.h"
@@ -71,6 +72,14 @@ typedef struct LogicalRepCtxStruct
 
 static LogicalRepCtxStruct *LogicalRepCtx;
 
+static void ApplyLauncherShmemRequest(void *arg);
+static void ApplyLauncherShmemInit(void *arg);
+
+const ShmemCallbacks ApplyLauncherShmemCallbacks = {
+	.request_fn = ApplyLauncherShmemRequest,
+	.init_fn = ApplyLauncherShmemInit,
+};
+
 /* an entry in the last-start-times shared hash table */
 typedef struct LauncherLastStartTimesEntry
 {
@@ -972,11 +981,11 @@ logicalrep_pa_worker_count(Oid subid)
 }
 
 /*
- * ApplyLauncherShmemSize
- *		Compute space needed for replication launcher shared memory
+ * ApplyLauncherShmemRequest
+ *		Register shared memory space needed for replication launcher
  */
-Size
-ApplyLauncherShmemSize(void)
+static void
+ApplyLauncherShmemRequest(void *arg)
 {
 	Size		size;
 
@@ -987,7 +996,10 @@ ApplyLauncherShmemSize(void)
 	size = MAXALIGN(size);
 	size = add_size(size, mul_size(max_logical_replication_workers,
 								   sizeof(LogicalRepWorker)));
-	return size;
+	ShmemRequestStruct(.name = "Logical Replication Launcher Data",
+					   .size = size,
+					   .ptr = (void **) &LogicalRepCtx,
+		);
 }
 
 /*
@@ -1028,35 +1040,23 @@ ApplyLauncherRegister(void)
 
 /*
  * ApplyLauncherShmemInit
- *		Allocate and initialize replication launcher shared memory
+ *		Initialize replication launcher shared memory
  */
-void
-ApplyLauncherShmemInit(void)
+static void
+ApplyLauncherShmemInit(void *arg)
 {
-	bool		found;
+	int			slot;
 
-	LogicalRepCtx = (LogicalRepCtxStruct *)
-		ShmemInitStruct("Logical Replication Launcher Data",
-						ApplyLauncherShmemSize(),
-						&found);
+	LogicalRepCtx->last_start_dsa = DSA_HANDLE_INVALID;
+	LogicalRepCtx->last_start_dsh = DSHASH_HANDLE_INVALID;
 
-	if (!found)
+	/* Initialize memory and spin locks for each worker slot. */
+	for (slot = 0; slot < max_logical_replication_workers; slot++)
 	{
-		int			slot;
-
-		memset(LogicalRepCtx, 0, ApplyLauncherShmemSize());
-
-		LogicalRepCtx->last_start_dsa = DSA_HANDLE_INVALID;
-		LogicalRepCtx->last_start_dsh = DSHASH_HANDLE_INVALID;
+		LogicalRepWorker *worker = &LogicalRepCtx->workers[slot];
 
-		/* Initialize memory and spin locks for each worker slot. */
-		for (slot = 0; slot < max_logical_replication_workers; slot++)
-		{
-			LogicalRepWorker *worker = &LogicalRepCtx->workers[slot];
-
-			memset(worker, 0, sizeof(LogicalRepWorker));
-			SpinLockInit(&worker->relmutex);
-		}
+		memset(worker, 0, sizeof(LogicalRepWorker));
+		SpinLockInit(&worker->relmutex);
 	}
 }
 
diff --git a/src/backend/replication/logical/logicalctl.c b/src/backend/replication/logical/logicalctl.c
index 4e292951201..72f68ec58ef 100644
--- a/src/backend/replication/logical/logicalctl.c
+++ b/src/backend/replication/logical/logicalctl.c
@@ -72,6 +72,7 @@
 #include "storage/proc.h"
 #include "storage/procarray.h"
 #include "storage/procsignal.h"
+#include "storage/subsystems.h"
 #include "utils/injection_point.h"
 
 /*
@@ -98,6 +99,12 @@ typedef struct LogicalDecodingCtlData
 
 static LogicalDecodingCtlData *LogicalDecodingCtl = NULL;
 
+static void LogicalDecodingCtlShmemRequest(void *arg);
+
+const ShmemCallbacks LogicalDecodingCtlShmemCallbacks = {
+	.request_fn = LogicalDecodingCtlShmemRequest,
+};
+
 /*
  * A process-local cache of LogicalDecodingCtl->xlog_logical_info. This is
  * initialized at process startup, and updated when processing the process
@@ -120,23 +127,13 @@ static void update_xlog_logical_info(void);
 static void abort_logical_decoding_activation(int code, Datum arg);
 static void write_logical_decoding_status_update_record(bool status);
 
-Size
-LogicalDecodingCtlShmemSize(void)
-{
-	return sizeof(LogicalDecodingCtlData);
-}
-
-void
-LogicalDecodingCtlShmemInit(void)
+static void
+LogicalDecodingCtlShmemRequest(void *arg)
 {
-	bool		found;
-
-	LogicalDecodingCtl = ShmemInitStruct("Logical decoding control",
-										 LogicalDecodingCtlShmemSize(),
-										 &found);
-
-	if (!found)
-		MemSet(LogicalDecodingCtl, 0, LogicalDecodingCtlShmemSize());
+	ShmemRequestStruct(.name = "Logical decoding control",
+					   .size = sizeof(LogicalDecodingCtlData),
+					   .ptr = (void **) &LogicalDecodingCtl,
+		);
 }
 
 /*
diff --git a/src/backend/replication/logical/origin.c b/src/backend/replication/logical/origin.c
index 661d68ad653..372d77c475e 100644
--- a/src/backend/replication/logical/origin.c
+++ b/src/backend/replication/logical/origin.c
@@ -88,6 +88,7 @@
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/lmgr.h"
+#include "storage/subsystems.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -176,6 +177,16 @@ ReplOriginXactState replorigin_xact_state = {
  */
 static ReplicationState *replication_states;
 
+static void ReplicationOriginShmemRequest(void *arg);
+static void ReplicationOriginShmemInit(void *arg);
+static void ReplicationOriginShmemAttach(void *arg);
+
+const ShmemCallbacks ReplicationOriginShmemCallbacks = {
+	.request_fn = ReplicationOriginShmemRequest,
+	.init_fn = ReplicationOriginShmemInit,
+	.attach_fn = ReplicationOriginShmemAttach,
+};
+
 /*
  * Actual shared memory block (replication_states[] is now part of this).
  */
@@ -539,50 +550,48 @@ replorigin_by_oid(ReplOriginId roident, bool missing_ok, char **roname)
  * ---------------------------------------------------------------------------
  */
 
-Size
-ReplicationOriginShmemSize(void)
+static void
+ReplicationOriginShmemRequest(void *arg)
 {
 	Size		size = 0;
 
 	if (max_active_replication_origins == 0)
-		return size;
+		return;
 
 	size = add_size(size, offsetof(ReplicationStateCtl, states));
-
 	size = add_size(size,
 					mul_size(max_active_replication_origins, sizeof(ReplicationState)));
-	return size;
+	ShmemRequestStruct(.name = "ReplicationOriginState",
+					   .size = size,
+					   .ptr = (void **) &replication_states_ctl,
+		);
 }
 
-void
-ReplicationOriginShmemInit(void)
+static void
+ReplicationOriginShmemInit(void *arg)
 {
-	bool		found;
-
 	if (max_active_replication_origins == 0)
 		return;
 
-	replication_states_ctl = (ReplicationStateCtl *)
-		ShmemInitStruct("ReplicationOriginState",
-						ReplicationOriginShmemSize(),
-						&found);
 	replication_states = replication_states_ctl->states;
 
-	if (!found)
-	{
-		int			i;
+	replication_states_ctl->tranche_id = LWTRANCHE_REPLICATION_ORIGIN_STATE;
 
-		MemSet(replication_states_ctl, 0, ReplicationOriginShmemSize());
+	for (int i = 0; i < max_active_replication_origins; i++)
+	{
+		LWLockInitialize(&replication_states[i].lock,
+						 replication_states_ctl->tranche_id);
+		ConditionVariableInit(&replication_states[i].origin_cv);
+	}
+}
 
-		replication_states_ctl->tranche_id = LWTRANCHE_REPLICATION_ORIGIN_STATE;
+static void
+ReplicationOriginShmemAttach(void *arg)
+{
+	if (max_active_replication_origins == 0)
+		return;
 
-		for (i = 0; i < max_active_replication_origins; i++)
-		{
-			LWLockInitialize(&replication_states[i].lock,
-							 replication_states_ctl->tranche_id);
-			ConditionVariableInit(&replication_states[i].origin_cv);
-		}
-	}
+	replication_states = replication_states_ctl->states;
 }
 
 /* ---------------------------------------------------------------------------
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e75db69e3f6..d615ff8a81c 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -73,6 +73,7 @@
 #include "storage/lmgr.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
+#include "storage/subsystems.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
 #include "utils/memutils.h"
@@ -118,6 +119,14 @@ typedef struct SlotSyncCtxStruct
 
 static SlotSyncCtxStruct *SlotSyncCtx = NULL;
 
+static void SlotSyncShmemRequest(void *arg);
+static void SlotSyncShmemInit(void *arg);
+
+const ShmemCallbacks SlotSyncShmemCallbacks = {
+	.request_fn = SlotSyncShmemRequest,
+	.init_fn = SlotSyncShmemInit,
+};
+
 /* GUC variable */
 bool		sync_replication_slots = false;
 
@@ -1828,32 +1837,26 @@ IsSyncingReplicationSlots(void)
 }
 
 /*
- * Amount of shared memory required for slot synchronization.
+ * Register shared memory space needed for slot synchronization.
  */
-Size
-SlotSyncShmemSize(void)
+static void
+SlotSyncShmemRequest(void *arg)
 {
-	return sizeof(SlotSyncCtxStruct);
+	ShmemRequestStruct(.name = "Slot Sync Data",
+					   .size = sizeof(SlotSyncCtxStruct),
+					   .ptr = (void **) &SlotSyncCtx,
+		);
 }
 
 /*
- * Allocate and initialize the shared memory of slot synchronization.
+ * Initialize shared memory for slot synchronization.
  */
-void
-SlotSyncShmemInit(void)
+static void
+SlotSyncShmemInit(void *arg)
 {
-	Size		size = SlotSyncShmemSize();
-	bool		found;
-
-	SlotSyncCtx = (SlotSyncCtxStruct *)
-		ShmemInitStruct("Slot Sync Data", size, &found);
-
-	if (!found)
-	{
-		memset(SlotSyncCtx, 0, size);
-		SlotSyncCtx->pid = InvalidPid;
-		SpinLockInit(&SlotSyncCtx->mutex);
-	}
+	memset(SlotSyncCtx, 0, sizeof(SlotSyncCtxStruct));
+	SlotSyncCtx->pid = InvalidPid;
+	SpinLockInit(&SlotSyncCtx->mutex);
 }
 
 /*
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..21a213a0ebf 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -55,6 +55,7 @@
 #include "storage/ipc.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
+#include "storage/subsystems.h"
 #include "utils/builtins.h"
 #include "utils/guc_hooks.h"
 #include "utils/injection_point.h"
@@ -145,6 +146,14 @@ StaticAssertDecl(lengthof(SlotInvalidationCauses) == (RS_INVAL_MAX_CAUSES + 1),
 /* Control array for replication slot management */
 ReplicationSlotCtlData *ReplicationSlotCtl = NULL;
 
+static void ReplicationSlotsShmemRequest(void *arg);
+static void ReplicationSlotsShmemInit(void *arg);
+
+const ShmemCallbacks ReplicationSlotsShmemCallbacks = {
+	.request_fn = ReplicationSlotsShmemRequest,
+	.init_fn = ReplicationSlotsShmemInit,
+};
+
 /* My backend's replication slot in the shared memory array */
 ReplicationSlot *MyReplicationSlot = NULL;
 
@@ -183,56 +192,41 @@ static void CreateSlotOnDisk(ReplicationSlot *slot);
 static void SaveSlotToPath(ReplicationSlot *slot, const char *dir, int elevel);
 
 /*
- * Report shared-memory space needed by ReplicationSlotsShmemInit.
+ * Register shared memory space needed for replication slots.
  */
-Size
-ReplicationSlotsShmemSize(void)
+static void
+ReplicationSlotsShmemRequest(void *arg)
 {
-	Size		size = 0;
+	Size		size;
 
 	if (max_replication_slots == 0)
-		return size;
+		return;
 
 	size = offsetof(ReplicationSlotCtlData, replication_slots);
 	size = add_size(size,
 					mul_size(max_replication_slots, sizeof(ReplicationSlot)));
-
-	return size;
+	ShmemRequestStruct(.name = "ReplicationSlot Ctl",
+					   .size = size,
+					   .ptr = (void **) &ReplicationSlotCtl,
+		);
 }
 
 /*
- * Allocate and initialize shared memory for replication slots.
+ * Initialize shared memory for replication slots.
  */
-void
-ReplicationSlotsShmemInit(void)
+static void
+ReplicationSlotsShmemInit(void *arg)
 {
-	bool		found;
-
-	if (max_replication_slots == 0)
-		return;
-
-	ReplicationSlotCtl = (ReplicationSlotCtlData *)
-		ShmemInitStruct("ReplicationSlot Ctl", ReplicationSlotsShmemSize(),
-						&found);
-
-	if (!found)
+	for (int i = 0; i < max_replication_slots; i++)
 	{
-		int			i;
+		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
-		/* First time through, so initialize */
-		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());
-
-		for (i = 0; i < max_replication_slots; i++)
-		{
-			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
-
-			/* everything else is zeroed by the memset above */
-			slot->active_proc = INVALID_PROC_NUMBER;
-			SpinLockInit(&slot->mutex);
-			LWLockInitialize(&slot->io_in_progress_lock,
-							 LWTRANCHE_REPLICATION_SLOT_IO);
-			ConditionVariableInit(&slot->active_cv);
-		}
+		/* everything else is zeroed by the memset above */
+		slot->active_proc = INVALID_PROC_NUMBER;
+		SpinLockInit(&slot->mutex);
+		LWLockInitialize(&slot->io_in_progress_lock,
+						 LWTRANCHE_REPLICATION_SLOT_IO);
+		ConditionVariableInit(&slot->active_cv);
 	}
 }
 
diff --git a/src/backend/replication/walreceiverfuncs.c b/src/backend/replication/walreceiverfuncs.c
index 45b9d4f09f2..4e03e721872 100644
--- a/src/backend/replication/walreceiverfuncs.c
+++ b/src/backend/replication/walreceiverfuncs.c
@@ -29,47 +29,46 @@
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
 #include "storage/shmem.h"
+#include "storage/subsystems.h"
 #include "utils/timestamp.h"
 #include "utils/wait_event.h"
 
 WalRcvData *WalRcv = NULL;
 
+static void WalRcvShmemRequest(void *arg);
+static void WalRcvShmemInit(void *arg);
+
+const ShmemCallbacks WalRcvShmemCallbacks = {
+	.request_fn = WalRcvShmemRequest,
+	.init_fn = WalRcvShmemInit,
+};
+
 /*
  * How long to wait for walreceiver to start up after requesting
  * postmaster to launch it. In seconds.
  */
 #define WALRCV_STARTUP_TIMEOUT 10
 
-/* Report shared memory space needed by WalRcvShmemInit */
-Size
-WalRcvShmemSize(void)
+/* Register shared memory space needed by walreceiver */
+static void
+WalRcvShmemRequest(void *arg)
 {
-	Size		size = 0;
-
-	size = add_size(size, sizeof(WalRcvData));
-
-	return size;
+	ShmemRequestStruct(.name = "Wal Receiver Ctl",
+					   .size = sizeof(WalRcvData),
+					   .ptr = (void **) &WalRcv,
+		);
 }
 
-/* Allocate and initialize walreceiver-related shared memory */
-void
-WalRcvShmemInit(void)
+/* Initialize walreceiver-related shared memory */
+static void
+WalRcvShmemInit(void *arg)
 {
-	bool		found;
-
-	WalRcv = (WalRcvData *)
-		ShmemInitStruct("Wal Receiver Ctl", WalRcvShmemSize(), &found);
-
-	if (!found)
-	{
-		/* First time through, so initialize */
-		MemSet(WalRcv, 0, WalRcvShmemSize());
-		WalRcv->walRcvState = WALRCV_STOPPED;
-		ConditionVariableInit(&WalRcv->walRcvStoppedCV);
-		SpinLockInit(&WalRcv->mutex);
-		pg_atomic_init_u64(&WalRcv->writtenUpto, 0);
-		WalRcv->procno = INVALID_PROC_NUMBER;
-	}
+	MemSet(WalRcv, 0, sizeof(WalRcvData));
+	WalRcv->walRcvState = WALRCV_STOPPED;
+	ConditionVariableInit(&WalRcv->walRcvStoppedCV);
+	SpinLockInit(&WalRcv->mutex);
+	pg_atomic_init_u64(&WalRcv->writtenUpto, 0);
+	WalRcv->procno = INVALID_PROC_NUMBER;
 }
 
 /* Is walreceiver running (or starting up)? */
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 2bb3f34dc6d..ec39942bfc1 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -86,6 +86,7 @@
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
+#include "storage/subsystems.h"
 #include "tcop/dest.h"
 #include "tcop/tcopprot.h"
 #include "utils/acl.h"
@@ -117,6 +118,14 @@
 /* Array of WalSnds in shared memory */
 WalSndCtlData *WalSndCtl = NULL;
 
+static void WalSndShmemRequest(void *arg);
+static void WalSndShmemInit(void *arg);
+
+const ShmemCallbacks WalSndShmemCallbacks = {
+	.request_fn = WalSndShmemRequest,
+	.init_fn = WalSndShmemInit,
+};
+
 /* My slot in the shared memory array */
 WalSnd	   *MyWalSnd = NULL;
 
@@ -3765,47 +3774,37 @@ WalSndSignals(void)
 	pqsignal(SIGCHLD, SIG_DFL);
 }
 
-/* Report shared-memory space needed by WalSndShmemInit */
-Size
-WalSndShmemSize(void)
+/* Register shared-memory space needed by walsender */
+static void
+WalSndShmemRequest(void *arg)
 {
-	Size		size = 0;
+	Size		size;
 
 	size = offsetof(WalSndCtlData, walsnds);
 	size = add_size(size, mul_size(max_wal_senders, sizeof(WalSnd)));
-
-	return size;
+	ShmemRequestStruct(.name = "Wal Sender Ctl",
+					   .size = size,
+					   .ptr = (void **) &WalSndCtl,
+		);
 }
 
-/* Allocate and initialize walsender-related shared memory */
-void
-WalSndShmemInit(void)
+/* Initialize walsender-related shared memory */
+static void
+WalSndShmemInit(void *arg)
 {
-	bool		found;
-	int			i;
+	for (int i = 0; i < NUM_SYNC_REP_WAIT_MODE; i++)
+		dlist_init(&(WalSndCtl->SyncRepQueue[i]));
 
-	WalSndCtl = (WalSndCtlData *)
-		ShmemInitStruct("Wal Sender Ctl", WalSndShmemSize(), &found);
-
-	if (!found)
+	for (int i = 0; i < max_wal_senders; i++)
 	{
-		/* First time through, so initialize */
-		MemSet(WalSndCtl, 0, WalSndShmemSize());
-
-		for (i = 0; i < NUM_SYNC_REP_WAIT_MODE; i++)
-			dlist_init(&(WalSndCtl->SyncRepQueue[i]));
-
-		for (i = 0; i < max_wal_senders; i++)
-		{
-			WalSnd	   *walsnd = &WalSndCtl->walsnds[i];
-
-			SpinLockInit(&walsnd->mutex);
-		}
+		WalSnd	   *walsnd = &WalSndCtl->walsnds[i];
 
-		ConditionVariableInit(&WalSndCtl->wal_flush_cv);
-		ConditionVariableInit(&WalSndCtl->wal_replay_cv);
-		ConditionVariableInit(&WalSndCtl->wal_confirm_rcv_cv);
+		SpinLockInit(&walsnd->mutex);
 	}
+
+	ConditionVariableInit(&WalSndCtl->wal_flush_cv);
+	ConditionVariableInit(&WalSndCtl->wal_replay_cv);
+	ConditionVariableInit(&WalSndCtl->wal_confirm_rcv_cv);
 }
 
 /*
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index f64c1d59fa3..bf6b81e621b 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -14,41 +14,16 @@
  */
 #include "postgres.h"
 
-#include "access/clog.h"
-#include "access/commit_ts.h"
-#include "access/multixact.h"
-#include "access/nbtree.h"
-#include "access/subtrans.h"
-#include "access/syncscan.h"
-#include "access/twophase.h"
-#include "access/xlogprefetcher.h"
-#include "access/xlogrecovery.h"
-#include "access/xlogwait.h"
-#include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
-#include "postmaster/autovacuum.h"
-#include "postmaster/bgworker_internals.h"
-#include "postmaster/bgwriter.h"
-#include "postmaster/datachecksum_state.h"
-#include "postmaster/walsummarizer.h"
-#include "replication/logicallauncher.h"
-#include "replication/origin.h"
-#include "replication/slot.h"
-#include "replication/slotsync.h"
-#include "replication/walreceiver.h"
-#include "replication/walsender.h"
-#include "storage/aio_subsys.h"
 #include "storage/dsm.h"
 #include "storage/ipc.h"
+#include "storage/lock.h"
 #include "storage/pg_shmem.h"
-#include "storage/predicate.h"
 #include "storage/proc.h"
 #include "storage/shmem_internal.h"
 #include "storage/subsystems.h"
 #include "utils/guc.h"
-#include "utils/injection_point.h"
-#include "utils/wait_event.h"
 
 /* GUCs */
 int			shared_memory_type = DEFAULT_SHARED_MEMORY_TYPE;
@@ -57,8 +32,6 @@ shmem_startup_hook_type shmem_startup_hook = NULL;
 
 static Size total_addin_request = 0;
 
-static void CreateOrAttachShmemStructs(void);
-
 /*
  * RequestAddinShmemSpace
  *		Request that extra shmem space be allocated for use by
@@ -97,33 +70,6 @@ CalculateShmemSize(void)
 	size = 100000;
 	size = add_size(size, ShmemGetRequestedSize());
 
-	/* legacy subsystems */
-	size = add_size(size, LockManagerShmemSize());
-	size = add_size(size, XLogPrefetchShmemSize());
-	size = add_size(size, XLOGShmemSize());
-	size = add_size(size, XLogRecoveryShmemSize());
-	size = add_size(size, TwoPhaseShmemSize());
-	size = add_size(size, BackgroundWorkerShmemSize());
-	size = add_size(size, BackendStatusShmemSize());
-	size = add_size(size, CheckpointerShmemSize());
-	size = add_size(size, AutoVacuumShmemSize());
-	size = add_size(size, ReplicationSlotsShmemSize());
-	size = add_size(size, ReplicationOriginShmemSize());
-	size = add_size(size, WalSndShmemSize());
-	size = add_size(size, WalRcvShmemSize());
-	size = add_size(size, WalSummarizerShmemSize());
-	size = add_size(size, PgArchShmemSize());
-	size = add_size(size, ApplyLauncherShmemSize());
-	size = add_size(size, BTreeShmemSize());
-	size = add_size(size, SyncScanShmemSize());
-	size = add_size(size, StatsShmemSize());
-	size = add_size(size, WaitEventCustomShmemSize());
-	size = add_size(size, InjectionPointShmemSize());
-	size = add_size(size, SlotSyncShmemSize());
-	size = add_size(size, WaitLSNShmemSize());
-	size = add_size(size, LogicalDecodingCtlShmemSize());
-	size = add_size(size, DataChecksumsShmemSize());
-
 	/* include additional requested shmem from preload libraries */
 	size = add_size(size, total_addin_request);
 
@@ -157,7 +103,6 @@ AttachSharedMemoryStructs(void)
 
 	/* Establish pointers to all shared memory areas in this backend */
 	ShmemAttachRequested();
-	CreateOrAttachShmemStructs();
 
 	/*
 	 * Now give loadable modules a chance to set up their shmem allocations
@@ -204,9 +149,6 @@ CreateSharedMemoryAndSemaphores(void)
 	/* Initialize all shmem areas */
 	ShmemInitRequested();
 
-	/* Initialize legacy subsystems */
-	CreateOrAttachShmemStructs();
-
 	/* Initialize dynamic shared memory facilities. */
 	dsm_postmaster_startup(shim);
 
@@ -237,70 +179,6 @@ RegisterBuiltinShmemCallbacks(void)
 #undef PG_SHMEM_SUBSYSTEM
 }
 
-/*
- * Initialize various subsystems, setting up their data structures in
- * shared memory.
- *
- * This is called by the postmaster or by a standalone backend.
- * It is also called by a backend forked from the postmaster in the
- * EXEC_BACKEND case.  In the latter case, the shared memory segment
- * already exists and has been physically attached to, but we have to
- * initialize pointers in local memory that reference the shared structures,
- * because we didn't inherit the correct pointer values from the postmaster
- * as we do in the fork() scenario.  The easiest way to do that is to run
- * through the same code as before.  (Note that the called routines mostly
- * check IsUnderPostmaster, rather than EXEC_BACKEND, to detect this case.
- * This is a bit code-wasteful and could be cleaned up.)
- */
-static void
-CreateOrAttachShmemStructs(void)
-{
-	/*
-	 * Set up xlog, clog, and buffers
-	 */
-	XLOGShmemInit();
-	XLogPrefetchShmemInit();
-	XLogRecoveryShmemInit();
-
-	/*
-	 * Set up lock manager
-	 */
-	LockManagerShmemInit();
-
-	/*
-	 * Set up process table
-	 */
-	BackendStatusShmemInit();
-	TwoPhaseShmemInit();
-	BackgroundWorkerShmemInit();
-
-	/*
-	 * Set up interprocess signaling mechanisms
-	 */
-	CheckpointerShmemInit();
-	AutoVacuumShmemInit();
-	ReplicationSlotsShmemInit();
-	ReplicationOriginShmemInit();
-	WalSndShmemInit();
-	WalRcvShmemInit();
-	WalSummarizerShmemInit();
-	PgArchShmemInit();
-	ApplyLauncherShmemInit();
-	SlotSyncShmemInit();
-	DataChecksumsShmemInit();
-
-	/*
-	 * Set up other modules that need some shared memory space
-	 */
-	BTreeShmemInit();
-	SyncScanShmemInit();
-	StatsShmemInit();
-	WaitEventCustomShmemInit();
-	InjectionPointShmemInit();
-	WaitLSNShmemInit();
-	LogicalDecodingCtlShmemInit();
-}
-
 /*
  * InitializeShmemGUCs
  *
diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c
index 798c453ab38..68d5a0389df 100644
--- a/src/backend/storage/lmgr/lock.c
+++ b/src/backend/storage/lmgr/lock.c
@@ -43,8 +43,10 @@
 #include "storage/lmgr.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
+#include "storage/shmem.h"
 #include "storage/spin.h"
 #include "storage/standby.h"
+#include "storage/subsystems.h"
 #include "utils/memutils.h"
 #include "utils/ps_status.h"
 #include "utils/resowner.h"
@@ -312,6 +314,14 @@ typedef struct
 
 static volatile FastPathStrongRelationLockData *FastPathStrongRelationLocks;
 
+static void LockManagerShmemRequest(void *arg);
+static void LockManagerShmemInit(void *arg);
+
+const ShmemCallbacks LockManagerShmemCallbacks = {
+	.request_fn = LockManagerShmemRequest,
+	.init_fn = LockManagerShmemInit,
+};
+
 
 /*
  * Pointers to hash tables containing lock state
@@ -409,6 +419,7 @@ PROCLOCK_PRINT(const char *where, const PROCLOCK *proclockP)
 
 
 static uint32 proclock_hash(const void *key, Size keysize);
+
 static void RemoveLocalLock(LOCALLOCK *locallock);
 static PROCLOCK *SetupLockInTable(LockMethod lockMethodTable, PGPROC *proc,
 								  const LOCKTAG *locktag, uint32 hashcode, LOCKMODE lockmode);
@@ -432,21 +443,15 @@ static void GetSingleProcBlockerStatusData(PGPROC *blocked_proc,
 
 
 /*
- * Initialize the lock manager's shmem data structures.
+ * Register the lock manager's shmem data structures.
  *
- * This is called from CreateSharedMemoryAndSemaphores(), which see for more
- * comments.  In the normal postmaster case, the shared hash tables are
- * created here, and backends inherit pointers to them via fork().  In the
- * EXEC_BACKEND case, each backend re-executes this code to obtain pointers to
- * the already existing shared hash tables.  In either case, each backend must
- * also call InitLockManagerAccess() to create the locallock hash table.
+ * In addition to this, each backend must also call InitLockManagerAccess() to
+ * create the locallock hash table.
  */
-void
-LockManagerShmemInit(void)
+static void
+LockManagerShmemRequest(void *arg)
 {
-	HASHCTL		info;
 	int64		max_table_size;
-	bool		found;
 
 	/*
 	 * Compute sizes for lock hashtables.  Note that these calculations must
@@ -455,45 +460,48 @@ LockManagerShmemInit(void)
 	max_table_size = NLOCKENTS();
 
 	/*
-	 * Allocate hash table for LOCK structs.  This stores per-locked-object
+	 * Hash table for LOCK structs.  This stores per-locked-object
 	 * information.
 	 */
-	info.keysize = sizeof(LOCKTAG);
-	info.entrysize = sizeof(LOCK);
-	info.num_partitions = NUM_LOCK_PARTITIONS;
-
-	LockMethodLockHash = ShmemInitHash("LOCK hash",
-									   max_table_size,
-									   &info,
-									   HASH_ELEM | HASH_BLOBS |
-									   HASH_PARTITION | HASH_FIXED_SIZE);
+	ShmemRequestHash(.name = "LOCK hash",
+					 .nelems = max_table_size,
+					 .ptr = &LockMethodLockHash,
+					 .hash_info.keysize = sizeof(LOCKTAG),
+					 .hash_info.entrysize = sizeof(LOCK),
+					 .hash_info.num_partitions = NUM_LOCK_PARTITIONS,
+					 .hash_flags = HASH_ELEM | HASH_BLOBS | HASH_PARTITION,
+		);
 
 	/* Assume an average of 2 holders per lock */
 	max_table_size *= 2;
 
-	/*
-	 * Allocate hash table for PROCLOCK structs.  This stores
-	 * per-lock-per-holder information.
-	 */
-	info.keysize = sizeof(PROCLOCKTAG);
-	info.entrysize = sizeof(PROCLOCK);
-	info.hash = proclock_hash;
-	info.num_partitions = NUM_LOCK_PARTITIONS;
-
-	LockMethodProcLockHash = ShmemInitHash("PROCLOCK hash",
-										   max_table_size,
-										   &info,
-										   HASH_ELEM | HASH_FUNCTION |
-										   HASH_FIXED_SIZE | HASH_PARTITION);
+	ShmemRequestHash(.name = "PROCLOCK hash",
+					 .nelems = max_table_size,
+					 .ptr = &LockMethodProcLockHash,
+					 .hash_info.keysize = sizeof(PROCLOCKTAG),
+					 .hash_info.entrysize = sizeof(PROCLOCK),
+					 .hash_info.hash = proclock_hash,
+					 .hash_info.num_partitions = NUM_LOCK_PARTITIONS,
+					 .hash_flags = HASH_ELEM | HASH_FUNCTION | HASH_PARTITION,
+		);
+
+	ShmemRequestStruct(.name = "Fast Path Strong Relation Lock Data",
+					   .size = sizeof(FastPathStrongRelationLockData),
+					   .ptr = (void **) (void *) &FastPathStrongRelationLocks,
+		);
 
 	/*
-	 * Allocate fast-path structures.
+	 * FIXME: we used to do this in the size calculation:
+	 *
+	 * // Since NLOCKENTS is only an estimate, add 10% safety margin. size =
+	 * add_size(size, size / 10);
 	 */
-	FastPathStrongRelationLocks =
-		ShmemInitStruct("Fast Path Strong Relation Lock Data",
-						sizeof(FastPathStrongRelationLockData), &found);
-	if (!found)
-		SpinLockInit(&FastPathStrongRelationLocks->mutex);
+}
+
+static void
+LockManagerShmemInit(void *arg)
+{
+	SpinLockInit(&FastPathStrongRelationLocks->mutex);
 }
 
 /*
@@ -3758,29 +3766,6 @@ PostPrepare_Locks(FullTransactionId fxid)
 }
 
 
-/*
- * Estimate shared-memory space used for lock tables
- */
-Size
-LockManagerShmemSize(void)
-{
-	Size		size = 0;
-	long		max_table_size;
-
-	/* lock hash table */
-	max_table_size = NLOCKENTS();
-	size = add_size(size, hash_estimate_size(max_table_size, sizeof(LOCK)));
-
-	/* proclock hash table */
-	max_table_size *= 2;
-	size = add_size(size, hash_estimate_size(max_table_size, sizeof(PROCLOCK)));
-
-	/* fast-path structures */
-	size = add_size(size, sizeof(FastPathStrongRelationLockData));
-
-	return size;
-}
-
 /*
  * GetLockStatusData - Return a summary of the lock manager's internal
  * status, for use in a user-level reporting function.
diff --git a/src/backend/utils/activity/backend_status.c b/src/backend/utils/activity/backend_status.c
index cd087129469..4cb9c80a2c5 100644
--- a/src/backend/utils/activity/backend_status.c
+++ b/src/backend/utils/activity/backend_status.c
@@ -18,7 +18,9 @@
 #include "pgstat.h"
 #include "storage/ipc.h"
 #include "storage/proc.h"		/* for MyProc */
+#include "storage/shmem.h"
 #include "storage/procarray.h"
+#include "storage/subsystems.h"
 #include "utils/ascii.h"
 #include "utils/guc.h"			/* for application_name */
 #include "utils/memutils.h"
@@ -73,133 +75,97 @@ static void pgstat_beshutdown_hook(int code, Datum arg);
 static void pgstat_read_current_status(void);
 static void pgstat_setup_backend_status_context(void);
 
+static void BackendStatusShmemRequest(void *arg);
+static void BackendStatusShmemInit(void *arg);
+static void BackendStatusShmemAttach(void *arg);
+
+const ShmemCallbacks BackendStatusShmemCallbacks = {
+	.request_fn = BackendStatusShmemRequest,
+	.init_fn = BackendStatusShmemInit,
+	.attach_fn = BackendStatusShmemAttach,
+};
 
 /*
- * Report shared-memory space needed by BackendStatusShmemInit.
+ * Register shared memory needs for backend status reporting.
  */
-Size
-BackendStatusShmemSize(void)
+static void
+BackendStatusShmemRequest(void *arg)
 {
-	Size		size;
-
-	/* BackendStatusArray: */
-	size = mul_size(sizeof(PgBackendStatus), NumBackendStatSlots);
-	/* BackendAppnameBuffer: */
-	size = add_size(size,
-					mul_size(NAMEDATALEN, NumBackendStatSlots));
-	/* BackendClientHostnameBuffer: */
-	size = add_size(size,
-					mul_size(NAMEDATALEN, NumBackendStatSlots));
-	/* BackendActivityBuffer: */
-	size = add_size(size,
-					mul_size(pgstat_track_activity_query_size, NumBackendStatSlots));
+	ShmemRequestStruct(.name = "Backend Status Array",
+					   .size = mul_size(sizeof(PgBackendStatus), NumBackendStatSlots),
+					   .ptr = (void **) &BackendStatusArray,
+		);
+
+	ShmemRequestStruct(.name = "Backend Application Name Buffer",
+					   .size = mul_size(NAMEDATALEN, NumBackendStatSlots),
+					   .ptr = (void **) &BackendAppnameBuffer,
+		);
+
+	ShmemRequestStruct(.name = "Backend Client Host Name Buffer",
+					   .size = mul_size(NAMEDATALEN, NumBackendStatSlots),
+					   .ptr = (void **) &BackendClientHostnameBuffer,
+		);
+
+	BackendActivityBufferSize = mul_size(pgstat_track_activity_query_size,
+										 NumBackendStatSlots);
+	ShmemRequestStruct(.name = "Backend Activity Buffer",
+					   .size = BackendActivityBufferSize,
+					   .ptr = (void **) &BackendActivityBuffer
+		);
+
 #ifdef USE_SSL
-	/* BackendSslStatusBuffer: */
-	size = add_size(size,
-					mul_size(sizeof(PgBackendSSLStatus), NumBackendStatSlots));
+	ShmemRequestStruct(.name = "Backend SSL Status Buffer",
+					   .size = mul_size(sizeof(PgBackendSSLStatus), NumBackendStatSlots),
+					   .ptr = (void **) &BackendSslStatusBuffer,
+		);
 #endif
+
 #ifdef ENABLE_GSS
-	/* BackendGssStatusBuffer: */
-	size = add_size(size,
-					mul_size(sizeof(PgBackendGSSStatus), NumBackendStatSlots));
+	ShmemRequestStruct(.name = "Backend GSS Status Buffer",
+					   .size = mul_size(sizeof(PgBackendGSSStatus), NumBackendStatSlots),
+					   .ptr = (void **) &BackendGssStatusBuffer,
+		);
 #endif
-	return size;
 }
 
 /*
  * Initialize the shared status array and several string buffers
  * during postmaster startup.
  */
-void
-BackendStatusShmemInit(void)
+static void
+BackendStatusShmemInit(void *arg)
 {
-	Size		size;
-	bool		found;
 	int			i;
 	char	   *buffer;
 
-	/* Create or attach to the shared array */
-	size = mul_size(sizeof(PgBackendStatus), NumBackendStatSlots);
-	BackendStatusArray = (PgBackendStatus *)
-		ShmemInitStruct("Backend Status Array", size, &found);
-
-	if (!found)
+	/* Initialize st_appname pointers. */
+	buffer = BackendAppnameBuffer;
+	for (i = 0; i < NumBackendStatSlots; i++)
 	{
-		/*
-		 * We're the first - initialize.
-		 */
-		MemSet(BackendStatusArray, 0, size);
-	}
-
-	/* Create or attach to the shared appname buffer */
-	size = mul_size(NAMEDATALEN, NumBackendStatSlots);
-	BackendAppnameBuffer = (char *)
-		ShmemInitStruct("Backend Application Name Buffer", size, &found);
-
-	if (!found)
-	{
-		MemSet(BackendAppnameBuffer, 0, size);
-
-		/* Initialize st_appname pointers. */
-		buffer = BackendAppnameBuffer;
-		for (i = 0; i < NumBackendStatSlots; i++)
-		{
-			BackendStatusArray[i].st_appname = buffer;
-			buffer += NAMEDATALEN;
-		}
+		BackendStatusArray[i].st_appname = buffer;
+		buffer += NAMEDATALEN;
 	}
 
-	/* Create or attach to the shared client hostname buffer */
-	size = mul_size(NAMEDATALEN, NumBackendStatSlots);
-	BackendClientHostnameBuffer = (char *)
-		ShmemInitStruct("Backend Client Host Name Buffer", size, &found);
-
-	if (!found)
+	/* Initialize st_clienthostname pointers. */
+	buffer = BackendClientHostnameBuffer;
+	for (i = 0; i < NumBackendStatSlots; i++)
 	{
-		MemSet(BackendClientHostnameBuffer, 0, size);
-
-		/* Initialize st_clienthostname pointers. */
-		buffer = BackendClientHostnameBuffer;
-		for (i = 0; i < NumBackendStatSlots; i++)
-		{
-			BackendStatusArray[i].st_clienthostname = buffer;
-			buffer += NAMEDATALEN;
-		}
+		BackendStatusArray[i].st_clienthostname = buffer;
+		buffer += NAMEDATALEN;
 	}
 
-	/* Create or attach to the shared activity buffer */
-	BackendActivityBufferSize = mul_size(pgstat_track_activity_query_size,
-										 NumBackendStatSlots);
-	BackendActivityBuffer = (char *)
-		ShmemInitStruct("Backend Activity Buffer",
-						BackendActivityBufferSize,
-						&found);
-
-	if (!found)
+	/* Initialize st_activity pointers. */
+	buffer = BackendActivityBuffer;
+	for (i = 0; i < NumBackendStatSlots; i++)
 	{
-		MemSet(BackendActivityBuffer, 0, BackendActivityBufferSize);
-
-		/* Initialize st_activity pointers. */
-		buffer = BackendActivityBuffer;
-		for (i = 0; i < NumBackendStatSlots; i++)
-		{
-			BackendStatusArray[i].st_activity_raw = buffer;
-			buffer += pgstat_track_activity_query_size;
-		}
+		BackendStatusArray[i].st_activity_raw = buffer;
+		buffer += pgstat_track_activity_query_size;
 	}
 
 #ifdef USE_SSL
-	/* Create or attach to the shared SSL status buffer */
-	size = mul_size(sizeof(PgBackendSSLStatus), NumBackendStatSlots);
-	BackendSslStatusBuffer = (PgBackendSSLStatus *)
-		ShmemInitStruct("Backend SSL Status Buffer", size, &found);
-
-	if (!found)
 	{
 		PgBackendSSLStatus *ptr;
 
-		MemSet(BackendSslStatusBuffer, 0, size);
-
 		/* Initialize st_sslstatus pointers. */
 		ptr = BackendSslStatusBuffer;
 		for (i = 0; i < NumBackendStatSlots; i++)
@@ -211,17 +177,9 @@ BackendStatusShmemInit(void)
 #endif
 
 #ifdef ENABLE_GSS
-	/* Create or attach to the shared GSSAPI status buffer */
-	size = mul_size(sizeof(PgBackendGSSStatus), NumBackendStatSlots);
-	BackendGssStatusBuffer = (PgBackendGSSStatus *)
-		ShmemInitStruct("Backend GSS Status Buffer", size, &found);
-
-	if (!found)
 	{
 		PgBackendGSSStatus *ptr;
 
-		MemSet(BackendGssStatusBuffer, 0, size);
-
 		/* Initialize st_gssstatus pointers. */
 		ptr = BackendGssStatusBuffer;
 		for (i = 0; i < NumBackendStatSlots; i++)
@@ -233,6 +191,13 @@ BackendStatusShmemInit(void)
 #endif
 }
 
+static void
+BackendStatusShmemAttach(void *arg)
+{
+	BackendActivityBufferSize = mul_size(pgstat_track_activity_query_size,
+										 NumBackendStatSlots);
+}
+
 /*
  * Initialize pgstats backend activity state, and set up our on-proc-exit
  * hook.  Called from InitPostgres and AuxiliaryProcessMain.  MyProcNumber must
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 33fbdca9609..955faf5ebc7 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -14,6 +14,7 @@
 
 #include "pgstat.h"
 #include "storage/shmem.h"
+#include "storage/subsystems.h"
 #include "utils/memutils.h"
 #include "utils/pgstat_internal.h"
 
@@ -57,6 +58,13 @@ static void pgstat_release_matching_entry_refs(bool discard_pending, ReleaseMatc
 
 static void pgstat_setup_memcxt(void);
 
+static void StatsShmemRequest(void *arg);
+static void StatsShmemInit(void *arg);
+
+const ShmemCallbacks StatsShmemCallbacks = {
+	.request_fn = StatsShmemRequest,
+	.init_fn = StatsShmemInit,
+};
 
 /* parameter for the shared hash */
 static const dshash_parameters dsh_params = {
@@ -123,7 +131,7 @@ pgstat_dsa_init_size(void)
 /*
  * Compute shared memory space needed for cumulative statistics
  */
-Size
+static Size
 StatsShmemSize(void)
 {
 	Size		sz;
@@ -149,102 +157,98 @@ StatsShmemSize(void)
 	return sz;
 }
 
+/*
+ * Register shared memory area for cumulative statistics
+ */
+static void
+StatsShmemRequest(void *arg)
+{
+	ShmemRequestStruct(.name = "Shared Memory Stats",
+					   .size = StatsShmemSize(),
+					   .ptr = (void **) &pgStatLocal.shmem,
+		);
+}
+
 /*
  * Initialize cumulative statistics system during startup
  */
-void
-StatsShmemInit(void)
+static void
+StatsShmemInit(void *arg)
 {
-	bool		found;
-	Size		sz;
+	dsa_area   *dsa;
+	dshash_table *dsh;
+	PgStat_ShmemControl *ctl = pgStatLocal.shmem;
+	char	   *p = (char *) ctl;
 
-	sz = StatsShmemSize();
-	pgStatLocal.shmem = (PgStat_ShmemControl *)
-		ShmemInitStruct("Shared Memory Stats", sz, &found);
+	/* the allocation of pgStatLocal.shmem itself */
+	p += MAXALIGN(sizeof(PgStat_ShmemControl));
 
-	if (!IsUnderPostmaster)
-	{
-		dsa_area   *dsa;
-		dshash_table *dsh;
-		PgStat_ShmemControl *ctl = pgStatLocal.shmem;
-		char	   *p = (char *) ctl;
+	/*
+	 * Create a small dsa allocation in plain shared memory. This is required
+	 * because postmaster cannot use dsm segments. It also provides a small
+	 * efficiency win.
+	 */
+	ctl->raw_dsa_area = p;
+	dsa = dsa_create_in_place(ctl->raw_dsa_area,
+							  pgstat_dsa_init_size(),
+							  LWTRANCHE_PGSTATS_DSA, NULL);
+	dsa_pin(dsa);
 
-		Assert(!found);
+	/*
+	 * To ensure dshash is created in "plain" shared memory, temporarily limit
+	 * size of dsa to the initial size of the dsa.
+	 */
+	dsa_set_size_limit(dsa, pgstat_dsa_init_size());
 
-		/* the allocation of pgStatLocal.shmem itself */
-		p += MAXALIGN(sizeof(PgStat_ShmemControl));
+	/*
+	 * With the limit in place, create the dshash table. XXX: It'd be nice if
+	 * there were dshash_create_in_place().
+	 */
+	dsh = dshash_create(dsa, &dsh_params, NULL);
+	ctl->hash_handle = dshash_get_hash_table_handle(dsh);
 
-		/*
-		 * Create a small dsa allocation in plain shared memory. This is
-		 * required because postmaster cannot use dsm segments. It also
-		 * provides a small efficiency win.
-		 */
-		ctl->raw_dsa_area = p;
-		dsa = dsa_create_in_place(ctl->raw_dsa_area,
-								  pgstat_dsa_init_size(),
-								  LWTRANCHE_PGSTATS_DSA, NULL);
-		dsa_pin(dsa);
+	/* lift limit set above */
+	dsa_set_size_limit(dsa, -1);
 
-		/*
-		 * To ensure dshash is created in "plain" shared memory, temporarily
-		 * limit size of dsa to the initial size of the dsa.
-		 */
-		dsa_set_size_limit(dsa, pgstat_dsa_init_size());
+	/*
+	 * Postmaster will never access these again, thus free the local
+	 * dsa/dshash references.
+	 */
+	dshash_detach(dsh);
+	dsa_detach(dsa);
 
-		/*
-		 * With the limit in place, create the dshash table. XXX: It'd be nice
-		 * if there were dshash_create_in_place().
-		 */
-		dsh = dshash_create(dsa, &dsh_params, NULL);
-		ctl->hash_handle = dshash_get_hash_table_handle(dsh);
+	pg_atomic_init_u64(&ctl->gc_request_count, 1);
 
-		/* lift limit set above */
-		dsa_set_size_limit(dsa, -1);
+	/* Do the per-kind initialization */
+	for (PgStat_Kind kind = PGSTAT_KIND_MIN; kind <= PGSTAT_KIND_MAX; kind++)
+	{
+		const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
+		char	   *ptr;
 
-		/*
-		 * Postmaster will never access these again, thus free the local
-		 * dsa/dshash references.
-		 */
-		dshash_detach(dsh);
-		dsa_detach(dsa);
+		if (!kind_info)
+			continue;
 
-		pg_atomic_init_u64(&ctl->gc_request_count, 1);
+		/* initialize entry count tracking */
+		if (kind_info->track_entry_count)
+			pg_atomic_init_u64(&ctl->entry_counts[kind - 1], 0);
 
-		/* Do the per-kind initialization */
-		for (PgStat_Kind kind = PGSTAT_KIND_MIN; kind <= PGSTAT_KIND_MAX; kind++)
+		/* initialize fixed-numbered stats */
+		if (kind_info->fixed_amount)
 		{
-			const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
-			char	   *ptr;
-
-			if (!kind_info)
-				continue;
-
-			/* initialize entry count tracking */
-			if (kind_info->track_entry_count)
-				pg_atomic_init_u64(&ctl->entry_counts[kind - 1], 0);
-
-			/* initialize fixed-numbered stats */
-			if (kind_info->fixed_amount)
+			if (pgstat_is_kind_builtin(kind))
+				ptr = ((char *) ctl) + kind_info->shared_ctl_off;
+			else
 			{
-				if (pgstat_is_kind_builtin(kind))
-					ptr = ((char *) ctl) + kind_info->shared_ctl_off;
-				else
-				{
-					int			idx = kind - PGSTAT_KIND_CUSTOM_MIN;
-
-					Assert(kind_info->shared_size != 0);
-					ctl->custom_data[idx] = ShmemAlloc(kind_info->shared_size);
-					ptr = ctl->custom_data[idx];
-				}
-
-				kind_info->init_shmem_cb(ptr);
+				int			idx = kind - PGSTAT_KIND_CUSTOM_MIN;
+
+				Assert(kind_info->shared_size != 0);
+				ctl->custom_data[idx] = ShmemAlloc(kind_info->shared_size);
+				ptr = ctl->custom_data[idx];
 			}
+
+			kind_info->init_shmem_cb(ptr);
 		}
 	}
-	else
-	{
-		Assert(found);
-	}
 }
 
 void
diff --git a/src/backend/utils/activity/wait_event.c b/src/backend/utils/activity/wait_event.c
index 2b76967776c..95635c7f56c 100644
--- a/src/backend/utils/activity/wait_event.c
+++ b/src/backend/utils/activity/wait_event.c
@@ -25,6 +25,7 @@
 #include "storage/lmgr.h"
 #include "storage/lwlock.h"
 #include "storage/shmem.h"
+#include "storage/subsystems.h"
 #include "storage/spin.h"
 #include "utils/wait_event.h"
 
@@ -95,59 +96,47 @@ static WaitEventCustomCounterData *WaitEventCustomCounter;
 static uint32 WaitEventCustomNew(uint32 classId, const char *wait_event_name);
 static const char *GetWaitEventCustomIdentifier(uint32 wait_event_info);
 
+static void WaitEventCustomShmemRequest(void *arg);
+static void WaitEventCustomShmemInit(void *arg);
+
+const ShmemCallbacks WaitEventCustomShmemCallbacks = {
+	.request_fn = WaitEventCustomShmemRequest,
+	.init_fn = WaitEventCustomShmemInit,
+};
+
 /*
- *  Return the space for dynamic shared hash tables and dynamic allocation counter.
+ * Register shmem space for dynamic shared hash and dynamic allocation counter.
  */
-Size
-WaitEventCustomShmemSize(void)
+static void
+WaitEventCustomShmemRequest(void *arg)
 {
-	Size		sz;
-
-	sz = MAXALIGN(sizeof(WaitEventCustomCounterData));
-	sz = add_size(sz, hash_estimate_size(WAIT_EVENT_CUSTOM_HASH_SIZE,
-										 sizeof(WaitEventCustomEntryByInfo)));
-	sz = add_size(sz, hash_estimate_size(WAIT_EVENT_CUSTOM_HASH_SIZE,
-										 sizeof(WaitEventCustomEntryByName)));
-	return sz;
+	ShmemRequestStruct(.name = "WaitEventCustomCounterData",
+					   .size = sizeof(WaitEventCustomCounterData),
+					   .ptr = (void **) &WaitEventCustomCounter,
+		);
+	ShmemRequestHash(.name = "WaitEventCustom hash by wait event information",
+					 .ptr = &WaitEventCustomHashByInfo,
+					 .nelems = WAIT_EVENT_CUSTOM_HASH_SIZE,
+					 .hash_info.keysize = sizeof(uint32),
+					 .hash_info.entrysize = sizeof(WaitEventCustomEntryByInfo),
+					 .hash_flags = HASH_ELEM | HASH_BLOBS,
+		);
+	ShmemRequestHash(.name = "WaitEventCustom hash by name",
+					 .ptr = &WaitEventCustomHashByName,
+					 .nelems = WAIT_EVENT_CUSTOM_HASH_SIZE,
+	/* key is a NULL-terminated string */
+					 .hash_info.keysize = sizeof(char[NAMEDATALEN]),
+					 .hash_info.entrysize = sizeof(WaitEventCustomEntryByName),
+					 .hash_flags = HASH_ELEM | HASH_STRINGS,
+		);
 }
 
-/*
- * Allocate shmem space for dynamic shared hash and dynamic allocation counter.
- */
-void
-WaitEventCustomShmemInit(void)
+static void
+WaitEventCustomShmemInit(void *arg)
 {
-	bool		found;
-	HASHCTL		info;
-
-	WaitEventCustomCounter = (WaitEventCustomCounterData *)
-		ShmemInitStruct("WaitEventCustomCounterData",
-						sizeof(WaitEventCustomCounterData), &found);
-
-	if (!found)
-	{
-		/* initialize the allocation counter and its spinlock. */
-		WaitEventCustomCounter->nextId = WAIT_EVENT_CUSTOM_INITIAL_ID;
-		SpinLockInit(&WaitEventCustomCounter->mutex);
-	}
-
-	/* initialize or attach the hash tables to store custom wait events */
-	info.keysize = sizeof(uint32);
-	info.entrysize = sizeof(WaitEventCustomEntryByInfo);
-	WaitEventCustomHashByInfo =
-		ShmemInitHash("WaitEventCustom hash by wait event information",
-					  WAIT_EVENT_CUSTOM_HASH_SIZE,
-					  &info,
-					  HASH_ELEM | HASH_BLOBS);
-
-	/* key is a NULL-terminated string */
-	info.keysize = sizeof(char[NAMEDATALEN]);
-	info.entrysize = sizeof(WaitEventCustomEntryByName);
-	WaitEventCustomHashByName =
-		ShmemInitHash("WaitEventCustom hash by name",
-					  WAIT_EVENT_CUSTOM_HASH_SIZE,
-					  &info,
-					  HASH_ELEM | HASH_STRINGS);
+	/* initialize the allocation counter and its spinlock. */
+	WaitEventCustomCounter->nextId = WAIT_EVENT_CUSTOM_INITIAL_ID;
+	SpinLockInit(&WaitEventCustomCounter->mutex);
 }
 
 /*
diff --git a/src/backend/utils/misc/injection_point.c b/src/backend/utils/misc/injection_point.c
index c06b0e9b800..a7c99e097ea 100644
--- a/src/backend/utils/misc/injection_point.c
+++ b/src/backend/utils/misc/injection_point.c
@@ -17,6 +17,7 @@
  */
 #include "postgres.h"
 
+#include "storage/subsystems.h"
 #include "utils/injection_point.h"
 
 #ifdef USE_INJECTION_POINTS
@@ -109,6 +110,11 @@ typedef struct InjectionPointCacheEntry
 
 static HTAB *InjectionPointCache = NULL;
 
+#ifdef USE_INJECTION_POINTS
+static void InjectionPointShmemRequest(void *arg);
+static void InjectionPointShmemInit(void *arg);
+#endif
+
 /*
  * injection_point_cache_add
  *
@@ -226,45 +232,34 @@ injection_point_cache_get(const char *name)
 }
 #endif							/* USE_INJECTION_POINTS */
 
-/*
- * Return the space for dynamic shared hash table.
- */
-Size
-InjectionPointShmemSize(void)
-{
+const ShmemCallbacks InjectionPointShmemCallbacks = {
 #ifdef USE_INJECTION_POINTS
-	Size		sz = 0;
-
-	sz = add_size(sz, sizeof(InjectionPointsCtl));
-	return sz;
-#else
-	return 0;
+	.request_fn = InjectionPointShmemRequest,
+	.init_fn = InjectionPointShmemInit,
 #endif
-}
+};
 
 /*
- * Allocate shmem space for dynamic shared hash.
+ * Reserve space for the dynamic shared hash table
  */
-void
-InjectionPointShmemInit(void)
-{
 #ifdef USE_INJECTION_POINTS
-	bool		found;
+static void
+InjectionPointShmemRequest(void *arg)
+{
+	ShmemRequestStruct(.name = "InjectionPoint hash",
+					   .size = sizeof(InjectionPointsCtl),
+					   .ptr = (void **) &ActiveInjectionPoints,
+		);
+}
 
-	ActiveInjectionPoints = ShmemInitStruct("InjectionPoint hash",
-											sizeof(InjectionPointsCtl),
-											&found);
-	if (!IsUnderPostmaster)
-	{
-		Assert(!found);
-		pg_atomic_init_u32(&ActiveInjectionPoints->max_inuse, 0);
-		for (int i = 0; i < MAX_INJECTION_POINTS; i++)
-			pg_atomic_init_u64(&ActiveInjectionPoints->entries[i].generation, 0);
-	}
-	else
-		Assert(found);
-#endif
+static void
+InjectionPointShmemInit(void *arg)
+{
+	pg_atomic_init_u32(&ActiveInjectionPoints->max_inuse, 0);
+	for (int i = 0; i < MAX_INJECTION_POINTS; i++)
+		pg_atomic_init_u64(&ActiveInjectionPoints->entries[i].generation, 0);
 }
+#endif
 
 /*
  * Attach a new injection point.
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index da7503c57b6..3097e9bb1af 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -1300,8 +1300,6 @@ extern BTCycleId _bt_vacuum_cycleid(Relation rel);
 extern BTCycleId _bt_start_vacuum(Relation rel);
 extern void _bt_end_vacuum(Relation rel);
 extern void _bt_end_vacuum_callback(int code, Datum arg);
-extern Size BTreeShmemSize(void);
-extern void BTreeShmemInit(void);
 extern bytea *btoptions(Datum reloptions, bool validate);
 extern bool btproperty(Oid index_oid, int attno,
 					   IndexAMProperty prop, const char *propname,
diff --git a/src/include/access/syncscan.h b/src/include/access/syncscan.h
index 24cf33294e5..32f8332aaee 100644
--- a/src/include/access/syncscan.h
+++ b/src/include/access/syncscan.h
@@ -24,7 +24,5 @@ extern PGDLLIMPORT bool trace_syncscan;
 
 extern void ss_report_location(Relation rel, BlockNumber location);
 extern BlockNumber ss_get_location(Relation rel, BlockNumber relnblocks);
-extern void SyncScanShmemInit(void);
-extern Size SyncScanShmemSize(void);
 
 #endif
diff --git a/src/include/access/twophase.h b/src/include/access/twophase.h
index 761d56a5f3d..1d2ff42c9b7 100644
--- a/src/include/access/twophase.h
+++ b/src/include/access/twophase.h
@@ -33,9 +33,6 @@ typedef struct GlobalTransactionData *GlobalTransaction;
 /* GUC variable */
 extern PGDLLIMPORT int max_prepared_xacts;
 
-extern Size TwoPhaseShmemSize(void);
-extern void TwoPhaseShmemInit(void);
-
 extern void AtAbort_Twophase(void);
 extern void PostPrepare_Twophase(void);
 
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index 4af38e74ce4..437b4f32349 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -259,8 +259,6 @@ extern void InitLocalDataChecksumState(void);
 extern void SetLocalDataChecksumState(uint32 data_checksum_version);
 extern bool GetDefaultCharSignedness(void);
 extern XLogRecPtr GetFakeLSNForUnloggedRel(void);
-extern Size XLOGShmemSize(void);
-extern void XLOGShmemInit(void);
 extern void BootStrapXLOG(uint32 data_checksum_version);
 extern void InitializeWalConsistencyChecking(void);
 extern void LocalProcessControlFile(bool reset);
diff --git a/src/include/access/xlogprefetcher.h b/src/include/access/xlogprefetcher.h
index 7ec40c4b78b..56a81676d92 100644
--- a/src/include/access/xlogprefetcher.h
+++ b/src/include/access/xlogprefetcher.h
@@ -34,9 +34,6 @@ typedef struct XLogPrefetcher XLogPrefetcher;
 
 extern void XLogPrefetchReconfigure(void);
 
-extern size_t XLogPrefetchShmemSize(void);
-extern void XLogPrefetchShmemInit(void);
-
 extern void XLogPrefetchResetStats(void);
 
 extern XLogPrefetcher *XLogPrefetcherAllocate(XLogReaderState *reader);
diff --git a/src/include/access/xlogrecovery.h b/src/include/access/xlogrecovery.h
index 2842106b285..ba7750dca0b 100644
--- a/src/include/access/xlogrecovery.h
+++ b/src/include/access/xlogrecovery.h
@@ -153,9 +153,6 @@ extern PGDLLIMPORT bool reachedConsistency;
 /* Are we currently in standby mode? */
 extern PGDLLIMPORT bool StandbyMode;
 
-extern Size XLogRecoveryShmemSize(void);
-extern void XLogRecoveryShmemInit(void);
-
 extern void InitWalRecovery(ControlFileData *ControlFile,
 							bool *wasShutdown_ptr, bool *haveBackupLabel_ptr,
 							bool *haveTblspcMap_ptr);
diff --git a/src/include/access/xlogwait.h b/src/include/access/xlogwait.h
index d12531d32b8..07157f220ea 100644
--- a/src/include/access/xlogwait.h
+++ b/src/include/access/xlogwait.h
@@ -100,8 +100,6 @@ typedef struct WaitLSNState
 
 extern PGDLLIMPORT WaitLSNState *waitLSNState;
 
-extern Size WaitLSNShmemSize(void);
-extern void WaitLSNShmemInit(void);
 extern XLogRecPtr GetCurrentLSNForWaitType(WaitLSNType lsnType);
 extern void WaitLSNWakeup(WaitLSNType lsnType, XLogRecPtr currentLSN);
 extern void WaitLSNCleanup(void);
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 8e3549c3752..2786a7c5ffb 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -541,10 +541,6 @@ typedef struct PgStat_BackendPending
  * Functions in pgstat.c
  */
 
-/* functions called from postmaster */
-extern Size StatsShmemSize(void);
-extern void StatsShmemInit(void);
-
 /* Functions called during server startup / shutdown */
 extern void pgstat_restore_stats(void);
 extern void pgstat_discard_stats(void);
diff --git a/src/include/postmaster/autovacuum.h b/src/include/postmaster/autovacuum.h
index b21d111d4d5..8954f6b28ee 100644
--- a/src/include/postmaster/autovacuum.h
+++ b/src/include/postmaster/autovacuum.h
@@ -66,8 +66,4 @@ pg_noreturn extern void AutoVacWorkerMain(const void *startup_data, size_t start
 extern bool AutoVacuumRequestWork(AutoVacuumWorkItemType type,
 								  Oid relationId, BlockNumber blkno);
 
-/* shared memory stuff */
-extern Size AutoVacuumShmemSize(void);
-extern void AutoVacuumShmemInit(void);
-
 #endif							/* AUTOVACUUM_H */
diff --git a/src/include/postmaster/bgworker_internals.h b/src/include/postmaster/bgworker_internals.h
index b789caf4034..b6261bc01df 100644
--- a/src/include/postmaster/bgworker_internals.h
+++ b/src/include/postmaster/bgworker_internals.h
@@ -41,8 +41,6 @@ typedef struct RegisteredBgWorker
 
 extern PGDLLIMPORT dlist_head BackgroundWorkerList;
 
-extern Size BackgroundWorkerShmemSize(void);
-extern void BackgroundWorkerShmemInit(void);
 extern void BackgroundWorkerStateChange(bool allow_new_workers);
 extern void ForgetBackgroundWorker(RegisteredBgWorker *rw);
 extern void ReportBackgroundWorkerPID(RegisteredBgWorker *rw);
diff --git a/src/include/postmaster/bgwriter.h b/src/include/postmaster/bgwriter.h
index 47470cba893..36eea0b1ab0 100644
--- a/src/include/postmaster/bgwriter.h
+++ b/src/include/postmaster/bgwriter.h
@@ -39,9 +39,6 @@ extern bool ForwardSyncRequest(const FileTag *ftag, SyncRequestType type);
 
 extern void AbsorbSyncRequests(void);
 
-extern Size CheckpointerShmemSize(void);
-extern void CheckpointerShmemInit(void);
-
 extern bool FirstCallSinceLastCheckpoint(void);
 
 #endif							/* _BGWRITER_H */
diff --git a/src/include/postmaster/datachecksum_state.h b/src/include/postmaster/datachecksum_state.h
index 343494edcc8..05625539604 100644
--- a/src/include/postmaster/datachecksum_state.h
+++ b/src/include/postmaster/datachecksum_state.h
@@ -17,10 +17,6 @@
 
 #include "storage/procsignal.h"
 
-/* Shared memory */
-extern Size DataChecksumsShmemSize(void);
-extern void DataChecksumsShmemInit(void);
-
 /* Possible operations the Datachecksumsworker can perform */
 typedef enum DataChecksumsWorkerOperation
 {
diff --git a/src/include/postmaster/pgarch.h b/src/include/postmaster/pgarch.h
index faa7609cd81..9772bb573a1 100644
--- a/src/include/postmaster/pgarch.h
+++ b/src/include/postmaster/pgarch.h
@@ -26,8 +26,6 @@
 #define MAX_XFN_CHARS	40
 #define VALID_XFN_CHARS "0123456789ABCDEF.history.backup.partial"
 
-extern Size PgArchShmemSize(void);
-extern void PgArchShmemInit(void);
 extern bool PgArchCanRestart(void);
 pg_noreturn extern void PgArchiverMain(const void *startup_data, size_t startup_data_len);
 extern void PgArchWakeup(void);
diff --git a/src/include/postmaster/walsummarizer.h b/src/include/postmaster/walsummarizer.h
index a4c055066b4..b9a755fadbc 100644
--- a/src/include/postmaster/walsummarizer.h
+++ b/src/include/postmaster/walsummarizer.h
@@ -19,8 +19,6 @@
 extern PGDLLIMPORT bool summarize_wal;
 extern PGDLLIMPORT int wal_summary_keep_time;
 
-extern Size WalSummarizerShmemSize(void);
-extern void WalSummarizerShmemInit(void);
 pg_noreturn extern void WalSummarizerMain(const void *startup_data, size_t startup_data_len);
 
 extern void GetWalSummarizerState(TimeLineID *summarized_tli,
diff --git a/src/include/replication/logicalctl.h b/src/include/replication/logicalctl.h
index 495554c532c..0bc1302f130 100644
--- a/src/include/replication/logicalctl.h
+++ b/src/include/replication/logicalctl.h
@@ -14,8 +14,6 @@
 #ifndef LOGICALCTL_H
 #define LOGICALCTL_H
 
-extern Size LogicalDecodingCtlShmemSize(void);
-extern void LogicalDecodingCtlShmemInit(void);
 extern void StartupLogicalDecodingStatus(bool last_status);
 extern void InitializeProcessXLogLogicalInfo(void);
 extern bool ProcessBarrierUpdateXLogLogicalInfo(void);
diff --git a/src/include/replication/logicallauncher.h b/src/include/replication/logicallauncher.h
index 504b710536a..5f0c1b9c682 100644
--- a/src/include/replication/logicallauncher.h
+++ b/src/include/replication/logicallauncher.h
@@ -19,9 +19,6 @@ extern PGDLLIMPORT int max_parallel_apply_workers_per_subscription;
 extern void ApplyLauncherRegister(void);
 extern void ApplyLauncherMain(Datum main_arg);
 
-extern Size ApplyLauncherShmemSize(void);
-extern void ApplyLauncherShmemInit(void);
-
 extern void ApplyLauncherForgetWorkerStartTime(Oid subid);
 
 extern void ApplyLauncherWakeupAtCommit(void);
diff --git a/src/include/replication/origin.h b/src/include/replication/origin.h
index eb46b41b4b7..a69faf6eaaf 100644
--- a/src/include/replication/origin.h
+++ b/src/include/replication/origin.h
@@ -84,8 +84,4 @@ extern void replorigin_redo(XLogReaderState *record);
 extern void replorigin_desc(StringInfo buf, XLogReaderState *record);
 extern const char *replorigin_identify(uint8 info);
 
-/* shared memory allocation */
-extern Size ReplicationOriginShmemSize(void);
-extern void ReplicationOriginShmemInit(void);
-
 #endif							/* PG_ORIGIN_H */
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 4b4709f6e2c..1a3557de607 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -327,10 +327,6 @@ extern PGDLLIMPORT int max_replication_slots;
 extern PGDLLIMPORT char *synchronized_standby_slots;
 extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
 
-/* shmem initialization functions */
-extern Size ReplicationSlotsShmemSize(void);
-extern void ReplicationSlotsShmemInit(void);
-
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
diff --git a/src/include/replication/slotsync.h b/src/include/replication/slotsync.h
index e546d0d050d..d2121cd3ed7 100644
--- a/src/include/replication/slotsync.h
+++ b/src/include/replication/slotsync.h
@@ -31,8 +31,6 @@ pg_noreturn extern void ReplSlotSyncWorkerMain(const void *startup_data, size_t
 extern void ShutDownSlotSync(void);
 extern bool SlotSyncWorkerCanRestart(void);
 extern bool IsSyncingReplicationSlots(void);
-extern Size SlotSyncShmemSize(void);
-extern void SlotSyncShmemInit(void);
 extern void SyncReplicationSlots(WalReceiverConn *wrconn);
 
 #endif							/* SLOTSYNC_H */
diff --git a/src/include/replication/walreceiver.h b/src/include/replication/walreceiver.h
index 85d24c87298..47c07574d4d 100644
--- a/src/include/replication/walreceiver.h
+++ b/src/include/replication/walreceiver.h
@@ -491,8 +491,6 @@ pg_noreturn extern void WalReceiverMain(const void *startup_data, size_t startup
 extern void WalRcvRequestApplyReply(void);
 
 /* prototypes for functions in walreceiverfuncs.c */
-extern Size WalRcvShmemSize(void);
-extern void WalRcvShmemInit(void);
 extern void ShutdownWalRcv(void);
 extern bool WalRcvStreaming(void);
 extern bool WalRcvRunning(void);
diff --git a/src/include/replication/walsender.h b/src/include/replication/walsender.h
index a4df3b8e0ae..8952c848d19 100644
--- a/src/include/replication/walsender.h
+++ b/src/include/replication/walsender.h
@@ -41,8 +41,6 @@ extern void WalSndErrorCleanup(void);
 extern void PhysicalWakeupLogicalWalSnd(void);
 extern XLogRecPtr GetStandbyFlushRecPtr(TimeLineID *tli);
 extern void WalSndSignals(void);
-extern Size WalSndShmemSize(void);
-extern void WalSndShmemInit(void);
 extern void WalSndWakeup(bool physical, bool logical);
 extern void WalSndInitStopping(void);
 extern void WalSndWaitStopping(void);
diff --git a/src/include/storage/lock.h b/src/include/storage/lock.h
index fa68e6ecece..ee3cb1dc203 100644
--- a/src/include/storage/lock.h
+++ b/src/include/storage/lock.h
@@ -375,8 +375,6 @@ typedef enum
 /*
  * function prototypes
  */
-extern void LockManagerShmemInit(void);
-extern Size LockManagerShmemSize(void);
 extern void InitLockManagerAccess(void);
 extern LockMethod GetLocksMethodTable(const LOCK *lock);
 extern LockMethod GetLockTagsMethodTable(const LOCKTAG *locktag);
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index d8e11756a61..5e092552c72 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -32,6 +32,9 @@ PG_SHMEM_SUBSYSTEM(DSMRegistryShmemCallbacks)
 
 /* xlog, clog, and buffers */
 PG_SHMEM_SUBSYSTEM(VarsupShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(XLOGShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(XLogPrefetchShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(XLogRecoveryShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(CLOGShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(CommitTsShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SUBTRANSShmemCallbacks)
@@ -40,12 +43,18 @@ PG_SHMEM_SUBSYSTEM(BufferManagerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(StrategyCtlShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(BufTableShmemCallbacks)
 
+/* lock manager */
+PG_SHMEM_SUBSYSTEM(LockManagerShmemCallbacks)
+
 /* predicate lock manager */
 PG_SHMEM_SUBSYSTEM(PredicateLockShmemCallbacks)
 
 /* process table */
 PG_SHMEM_SUBSYSTEM(ProcGlobalShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ProcArrayShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(BackendStatusShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(TwoPhaseShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(BackgroundWorkerShmemCallbacks)
 
 /* shared-inval messaging */
 PG_SHMEM_SUBSYSTEM(SharedInvalShmemCallbacks)
@@ -53,9 +62,27 @@ PG_SHMEM_SUBSYSTEM(SharedInvalShmemCallbacks)
 /* interprocess signaling mechanisms */
 PG_SHMEM_SUBSYSTEM(PMSignalShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ProcSignalShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(CheckpointerShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(AutoVacuumShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(ReplicationSlotsShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(ReplicationOriginShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(WalSndShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(WalRcvShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
 
 /* other modules that need some shared memory space */
+PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(SyncScanShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(AsyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(StatsShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(WaitEventCustomShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(InjectionPointShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(WaitLSNShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(LogicalDecodingCtlShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(DataChecksumsShmemCallbacks)
 
 /* AIO subsystem. This delegates to the method-specific callbacks */
 PG_SHMEM_SUBSYSTEM(AioShmemCallbacks)
diff --git a/src/include/utils/backend_status.h b/src/include/utils/backend_status.h
index ddd06304e97..a334e096e4a 100644
--- a/src/include/utils/backend_status.h
+++ b/src/include/utils/backend_status.h
@@ -298,14 +298,6 @@ extern PGDLLIMPORT int pgstat_track_activity_query_size;
 extern PGDLLIMPORT PgBackendStatus *MyBEEntry;
 
 
-/* ----------
- * Functions called from postmaster
- * ----------
- */
-extern Size BackendStatusShmemSize(void);
-extern void BackendStatusShmemInit(void);
-
-
 /* ----------
  * Functions called from backends
  * ----------
diff --git a/src/include/utils/injection_point.h b/src/include/utils/injection_point.h
index 27a2526524f..fabd1455c3c 100644
--- a/src/include/utils/injection_point.h
+++ b/src/include/utils/injection_point.h
@@ -46,9 +46,6 @@ typedef void (*InjectionPointCallback) (const char *name,
 										const void *private_data,
 										void *arg);
 
-extern Size InjectionPointShmemSize(void);
-extern void InjectionPointShmemInit(void);
-
 extern void InjectionPointAttach(const char *name,
 								 const char *library,
 								 const char *function,
diff --git a/src/include/utils/wait_event.h b/src/include/utils/wait_event.h
index 34c27cc3dc3..86ee348220d 100644
--- a/src/include/utils/wait_event.h
+++ b/src/include/utils/wait_event.h
@@ -42,8 +42,6 @@ extern PGDLLIMPORT uint32 *my_wait_event_info;
 extern uint32 WaitEventExtensionNew(const char *wait_event_name);
 extern uint32 WaitEventInjectionPointNew(const char *wait_event_name);
 
-extern void WaitEventCustomShmemInit(void);
-extern Size WaitEventCustomShmemSize(void);
 extern char **GetWaitEventCustomNames(uint32 classId, int *nwaitevents);
 
 /* ----------
diff --git a/src/test/modules/injection_points/injection_points.c b/src/test/modules/injection_points/injection_points.c
index d59c5ad0582..0f1af513673 100644
--- a/src/test/modules/injection_points/injection_points.c
+++ b/src/test/modules/injection_points/injection_points.c
@@ -107,9 +107,13 @@ extern PGDLLEXPORT void injection_wait(const char *name,
 /* track if injection points attached in this process are linked to it */
 static bool injection_point_local = false;
 
-/* Shared memory init callbacks */
-static shmem_request_hook_type prev_shmem_request_hook = NULL;
-static shmem_startup_hook_type prev_shmem_startup_hook = NULL;
+static void injection_shmem_request(void *arg);
+static void injection_shmem_init(void *arg);
+
+static const ShmemCallbacks injection_shmem_callbacks = {
+	.request_fn = injection_shmem_request,
+	.init_fn = injection_shmem_init,
+};
 
 /*
  * Routine for shared memory area initialization, used as a callback
@@ -126,44 +130,23 @@ injection_point_init_state(void *ptr, void *arg)
 	ConditionVariableInit(&state->wait_point);
 }
 
-/* Shared memory initialization when loading module */
 static void
-injection_shmem_request(void)
+injection_shmem_request(void *arg)
 {
-	Size		size;
-
-	if (prev_shmem_request_hook)
-		prev_shmem_request_hook();
-
-	size = MAXALIGN(sizeof(InjectionPointSharedState));
-	RequestAddinShmemSpace(size);
+	ShmemRequestStruct(.name = "injection_points",
+					   .size = sizeof(InjectionPointSharedState),
+					   .ptr = (void **) &inj_state,
+		);
 }
 
 static void
-injection_shmem_startup(void)
+injection_shmem_init(void *arg)
 {
-	bool		found;
-
-	if (prev_shmem_startup_hook)
-		prev_shmem_startup_hook();
-
-	/* Create or attach to the shared memory state */
-	LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
-
-	inj_state = ShmemInitStruct("injection_points",
-								sizeof(InjectionPointSharedState),
-								&found);
-
-	if (!found)
-	{
-		/*
-		 * First time through, so initialize.  This is shared with the dynamic
-		 * initialization using a DSM.
-		 */
-		injection_point_init_state(inj_state, NULL);
-	}
-
-	LWLockRelease(AddinShmemInitLock);
+	/*
+	 * First time through, so initialize.  This is shared with the dynamic
+	 * initialization using a DSM.
+	 */
+	injection_point_init_state(inj_state, NULL);
 }
 
 /*
@@ -601,9 +584,5 @@ _PG_init(void)
 	if (!process_shared_preload_libraries_in_progress)
 		return;
 
-	/* Shared memory initialization */
-	prev_shmem_request_hook = shmem_request_hook;
-	shmem_request_hook = injection_shmem_request;
-	prev_shmem_startup_hook = shmem_startup_hook;
-	shmem_startup_hook = injection_shmem_startup;
+	RegisterShmemCallbacks(&injection_shmem_callbacks);
 }
diff --git a/src/test/modules/test_aio/test_aio.c b/src/test/modules/test_aio/test_aio.c
index d7530681192..35efba1a5e3 100644
--- a/src/test/modules/test_aio/test_aio.c
+++ b/src/test/modules/test_aio/test_aio.c
@@ -28,7 +28,6 @@
 #include "storage/bufmgr.h"
 #include "storage/checksum.h"
 #include "storage/condition_variable.h"
-#include "storage/ipc.h"
 #include "storage/lwlock.h"
 #include "storage/proc.h"
 #include "storage/procnumber.h"
@@ -44,6 +43,7 @@
 PG_MODULE_MAGIC;
 
 
+/* In shared memory */
 typedef struct InjIoErrorState
 {
 	ConditionVariable cv;
@@ -74,8 +74,15 @@ typedef struct BlocksReadStreamData
 static InjIoErrorState *inj_io_error_state;
 
 /* Shared memory init callbacks */
-static shmem_request_hook_type prev_shmem_request_hook = NULL;
-static shmem_startup_hook_type prev_shmem_startup_hook = NULL;
+static void test_aio_shmem_request(void *arg);
+static void test_aio_shmem_init(void *arg);
+static void test_aio_shmem_attach(void *arg);
+
+static const ShmemCallbacks inj_io_shmem_callbacks = {
+	.request_fn = test_aio_shmem_request,
+	.init_fn = test_aio_shmem_init,
+	.attach_fn = test_aio_shmem_attach,
+};
 
 
 static PgAioHandle *last_handle;
@@ -83,70 +90,55 @@ static PgAioHandle *last_handle;
 
 
 static void
-test_aio_shmem_request(void)
+test_aio_shmem_request(void *arg)
 {
-	if (prev_shmem_request_hook)
-		prev_shmem_request_hook();
-
-	RequestAddinShmemSpace(sizeof(InjIoErrorState));
+	ShmemRequestStruct(.name = "test_aio injection points",
+					   .size = sizeof(InjIoErrorState),
+					   .ptr = (void **) &inj_io_error_state,
+		);
 }
 
 static void
-test_aio_shmem_startup(void)
+test_aio_shmem_init(void *arg)
 {
-	bool		found;
-
-	if (prev_shmem_startup_hook)
-		prev_shmem_startup_hook();
-
-	/* Create or attach to the shared memory state */
-	LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
-
-	inj_io_error_state = ShmemInitStruct("injection_points",
-										 sizeof(InjIoErrorState),
-										 &found);
-
-	if (!found)
-	{
-		/* First time through, initialize */
-		inj_io_error_state->enabled_short_read = false;
-		inj_io_error_state->enabled_reopen = false;
-		inj_io_error_state->enabled_completion_wait = false;
+	/* First time through, initialize */
+	inj_io_error_state->enabled_short_read = false;
+	inj_io_error_state->enabled_reopen = false;
+	inj_io_error_state->enabled_completion_wait = false;
 
-		ConditionVariableInit(&inj_io_error_state->cv);
-		inj_io_error_state->completion_wait_event = WaitEventInjectionPointNew("completion_wait");
+	ConditionVariableInit(&inj_io_error_state->cv);
+	inj_io_error_state->completion_wait_event = WaitEventInjectionPointNew("completion_wait");
 
 #ifdef USE_INJECTION_POINTS
-		InjectionPointAttach("aio-process-completion-before-shared",
-							 "test_aio",
-							 "inj_io_completion_hook",
-							 NULL,
-							 0);
-		InjectionPointLoad("aio-process-completion-before-shared");
-
-		InjectionPointAttach("aio-worker-after-reopen",
-							 "test_aio",
-							 "inj_io_reopen",
-							 NULL,
-							 0);
-		InjectionPointLoad("aio-worker-after-reopen");
+	InjectionPointAttach("aio-process-completion-before-shared",
+						 "test_aio",
+						 "inj_io_completion_hook",
+						 NULL,
+						 0);
+	InjectionPointLoad("aio-process-completion-before-shared");
+
+	InjectionPointAttach("aio-worker-after-reopen",
+						 "test_aio",
+						 "inj_io_reopen",
+						 NULL,
+						 0);
+	InjectionPointLoad("aio-worker-after-reopen");
 
 #endif
-	}
-	else
-	{
-		/*
-		 * Pre-load the injection points now, so we can call them in a
-		 * critical section.
-		 */
+}
+
+static void
+test_aio_shmem_attach(void *arg)
+{
+	/*
+	 * Pre-load the injection points now, so we can call them in a critical
+	 * section.
+	 */
 #ifdef USE_INJECTION_POINTS
-		InjectionPointLoad("aio-process-completion-before-shared");
-		InjectionPointLoad("aio-worker-after-reopen");
-		elog(LOG, "injection point loaded");
+	InjectionPointLoad("aio-process-completion-before-shared");
+	InjectionPointLoad("aio-worker-after-reopen");
+	elog(LOG, "injection point loaded");
 #endif
-	}
-
-	LWLockRelease(AddinShmemInitLock);
 }
 
 void
@@ -155,10 +147,7 @@ _PG_init(void)
 	if (!process_shared_preload_libraries_in_progress)
 		return;
 
-	prev_shmem_request_hook = shmem_request_hook;
-	shmem_request_hook = test_aio_shmem_request;
-	prev_shmem_startup_hook = shmem_startup_hook;
-	shmem_startup_hook = test_aio_shmem_startup;
+	RegisterShmemCallbacks(&inj_io_shmem_callbacks);
 }
 
 
-- 
2.34.1



  [text/x-patch] v20260405-0015-resizable-shared-memory-structures.patch (66.2K, 16-v20260405-0015-resizable-shared-memory-structures.patch)
  download | inline diff:
From 77ca1ea3f27d79e8c59a98b89ee46d3dd17be2dc Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email protected]>
Date: Tue, 17 Feb 2026 16:51:20 +0530
Subject: [PATCH v20260405 15/15] resizable shared memory structures

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

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

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

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

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

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

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

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



view thread (82+ messages)  latest in thread

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: [email protected]
  Cc: [email protected], [email protected], [email protected], [email protected], [email protected], [email protected]
  Subject: Re: Better shared data structure management and resizable shared data structures
  In-Reply-To: <CAExHW5stth2mdXh3ukn9rWJ+Pruoat+5tY3CYyM6KGqH2G30fQ@mail.gmail.com>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox