From d2d065d866f7275b8e6706757240e39a0e34d2c0 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
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 <ashutosh.bapat.oss@gmail.com>
Reviewed-by: Zsolt Parragi <zsolt.parragi@percona.com>
Reviewed-by: Robert Haas <robertmhaas@gmail.com>
Reviewed-by: Daniel Gustafsson <daniel@yesql.se>
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

