From fac682ae503a5d03795145328e2bc4bbcae2feb9 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 02/14] 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       |   1 +
 src/backend/postmaster/launch_backend.c |   4 +
 src/backend/postmaster/postmaster.c     |  18 +-
 src/backend/storage/ipc/ipci.c          |  29 +-
 src/backend/storage/ipc/shmem.c         | 798 ++++++++++++++++++++----
 src/backend/storage/ipc/shmem_hash.c    |  77 ++-
 src/backend/storage/lmgr/proc.c         |   3 +
 src/backend/tcop/postgres.c             |   9 +-
 src/include/storage/shmem.h             | 213 ++++++-
 src/test/modules/test_aio/test_aio.c    |   1 -
 src/tools/pgindent/typedefs.list        |   9 +-
 13 files changed, 1129 insertions(+), 199 deletions(-)

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..26d3717c2cb 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -373,6 +373,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..75423104be8 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -49,6 +49,7 @@
 #include "replication/walreceiver.h"
 #include "storage/dsm.h"
 #include "storage/io_worker.h"
+#include "storage/ipc.h"
 #include "storage/pg_shmem.h"
 #include "tcop/backend_startup.h"
 #include "utils/memutils.h"
@@ -672,7 +673,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..01b064d62ea 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -951,7 +951,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 +3239,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..5333e528e1f 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -100,8 +100,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 +177,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 +228,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 +269,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..606b545e8fe 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"
@@ -74,6 +146,76 @@
 #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
+{
+	ShmemStructDesc *desc;
+	ShmemStructOpts *options;
+	ShmemAreaKind 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,393 @@ 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		 (64)
+
+/* 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 bool AttachOrInitShmemIndexEntry(ShmemRequest *request,
+										bool may_init, bool may_attach);
+
 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(ShmemStructDesc *desc, const ShmemStructOpts *options)
+{
+	ShmemStructOpts *options_copy;
+
+	options_copy = MemoryContextAlloc(TopMemoryContext,
+									  sizeof(ShmemStructOpts));
+	memcpy(options_copy, options, sizeof(ShmemStructOpts));
+
+	ShmemRequestInternal(desc, 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(ShmemStructDesc *desc, ShmemStructOpts *options,
+					 ShmemAreaKind 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(ShmemStructDesc, existing, pending_shmem_requests)
+	{
+		if (strcmp(existing->name, options->name) == 0)
+			ereport(ERROR,
+					(errmsg("shared memory struct \"%s\" is already registered",
+							options->name)));
+	}
+
+	request = palloc(sizeof(ShmemRequest));
+	request->options = options;
+	request->desc = desc;
+	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));
+
+	/* memory needed for all the requested areas */
+	foreach_ptr(ShmemRequest, request, pending_shmem_requests)
+	{
+		size = add_size(size, request->options->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)
+	{
+		AttachOrInitShmemIndexEntry(request, true, false);
+	}
+	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)
+	{
+		AttachOrInitShmemIndexEntry(request, false, true);
+	}
+	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
+
+/*
+ * Workhorse to insert or look up a named shmem area in the shared memory
+ * index, and initialize or attach to it.
+ *
+ * Note that this only does the basic initialization depending ShmemAreaKind,
+ * 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 callbacks specific to the subsystem that requested it.  That's
+ * done later after all the shmem areas have been initialized or attached to.
+ *
+ * may_init == true && may_attach == false is used at postmaster startup to
+ * allocate all the areas.  An error is thrown if the area already exists.
+ *
+ * may_init == false && may_attach == true is used at backend startup in
+ * EXEC_BACKEND mode to attach to all the areas.  The area is expected to
+ * already be initialized, an error is thrown if not.
+ *
+ * may_init == true && may_attach == true is used when a shared memory is
+ * requested after startup, with SHMEM_CALLBACKS_ALLOW_AFTER_STARTUP.
+ *
+ * (may_init == false && may_attach == false is not used, it would always
+ * raise an error)
  */
-typedef struct shmem_hash_allocator
+static bool
+AttachOrInitShmemIndexEntry(ShmemRequest *request,
+							bool may_init, bool may_attach)
 {
-	char	   *next;			/* start of free space in the area */
-	char	   *end;			/* end of the shmem area */
-} shmem_hash_allocator;
+	/*
+	 * If called after postmaster startup, we need to immediately also
+	 * initialize or attach to the area.
+	 */
+	ShmemStructDesc *desc = request->desc;
+	ShmemIndexEnt *index_entry;
+	bool		found;
+
+	/* If both are false, we'll fail no matter what */
+	Assert(may_init || may_attach);
+
+	desc->name = request->options->name;
+	desc->ptr = NULL;
+
+	/* look it up in the shmem index */
+	index_entry = (ShmemIndexEnt *)
+		hash_search(ShmemIndex, request->options->name,
+					may_init ? HASH_ENTER_NULL : HASH_FIND, &found);
+	if (found)
+	{
+		/* Already present, just attach to it */
+		if (!may_attach)
+			elog(ERROR, "shared memory struct \"%s\" is already initialized", desc->name);
+
+		if (index_entry->size != request->options->size &&
+			request->options->size != SHMEM_ATTACH_UNKNOWN_SIZE)
+		{
+			elog(ERROR, "shared memory struct \"%s\" is already registered with different size",
+				 desc->name);
+		}
+		desc->ptr = index_entry->location;
+		desc->size = index_entry->size;
+
+		/* 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_attach(desc, request->options);
+				break;
+		}
+	}
+	else if (!may_init)
+	{
+		/* attach was requested, but it was not found */
+		ereport(ERROR,
+				(errcode(ERRCODE_OUT_OF_MEMORY),
+				 errmsg("could not find ShmemIndex entry for data structure \"%s\"",
+						desc->name)));
+	}
+	else 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\"",
+						desc->name)));
+	}
+	else
+	{
+		/*
+		 * We inserted the entry to the shared memory index. Allocate
+		 * requested amount of shared memory for it, and do basic
+		 * initializion.
+		 */
+		size_t		allocated_size;
+		void	   *structPtr;
+
+		structPtr = ShmemAllocRaw(request->options->size, &allocated_size);
+		if (structPtr == NULL)
+		{
+			/* out of memory; remove the failed ShmemIndex entry */
+			hash_search(ShmemIndex, desc->name, HASH_REMOVE, NULL);
+			ereport(ERROR,
+					(errcode(ERRCODE_OUT_OF_MEMORY),
+					 errmsg("not enough shared memory for data structure"
+							" \"%s\" (%zu bytes requested)",
+							desc->name, request->options->size)));
+		}
+		index_entry->size = request->options->size;
+		index_entry->allocated_size = allocated_size;
+		index_entry->location = structPtr;
+
+		desc->ptr = index_entry->location;
+		desc->size = index_entry->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_init(desc, request->options);
+				break;
+		}
+	}
+
+	return found;
+}
 
 /*
  *	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 +642,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 +696,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 +731,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 +845,141 @@ 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 */
+		bool		found = false;
 
-	Assert(ShmemIndex != NULL);
+		if ((callbacks->flags & SHMEM_CALLBACKS_ALLOW_AFTER_STARTUP) == 0)
+			elog(ERROR, "cannot request shared memory at this time");
 
-	LWLockAcquire(ShmemIndexLock, LW_EXCLUSIVE);
+		Assert(pending_shmem_requests == NIL);
+		Assert(shmem_request_state == SRS_DONE);
+		shmem_request_state = SRS_REQUESTING;
 
-	/* look it up in the shmem index */
-	result = (ShmemIndexEnt *)
-		hash_search(ShmemIndex, name, HASH_ENTER_NULL, foundPtr);
+		if (callbacks->request_fn)
+			callbacks->request_fn(callbacks->request_fn_arg);
+		shmem_request_state = SRS_AFTER_STARTUP_ATTACH_OR_INIT;
 
-	if (!result)
-	{
-		LWLockRelease(ShmemIndexLock);
-		ereport(ERROR,
-				(errcode(ERRCODE_OUT_OF_MEMORY),
-				 errmsg("could not create ShmemIndex entry for data structure \"%s\"",
-						name)));
-	}
+		LWLockAcquire(ShmemIndexLock, LW_EXCLUSIVE);
 
-	if (*foundPtr)
-	{
 		/*
-		 * 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).
+		 * Allocate or attach all the shmem areas requested by the request_fn
+		 * callback.
 		 */
-		if (result->size != size)
+		foreach_ptr(ShmemRequest, request, pending_shmem_requests)
 		{
-			LWLockRelease(ShmemIndexLock);
-			ereport(ERROR,
-					(errmsg("ShmemIndex entry size is wrong for data structure"
-							" \"%s\": expected %zu, actual %zu",
-							name, size, result->size)));
+			found = AttachOrInitShmemIndexEntry(request, true, true);
 		}
-		structPtr = result->location;
-	}
-	else
-	{
-		Size		allocated_size;
+		list_free_deep(pending_shmem_requests);
+		pending_shmem_requests = NIL;
 
-		/* It isn't in the table yet. allocate and initialize it */
-		structPtr = ShmemAllocRaw(size, &allocated_size);
-		if (structPtr == NULL)
+		/*
+		 * Finish initialization or attaching to the shmem ares by calling the
+		 * appropriate callback.
+		 *
+		 * FIXME: What to do if multiple shmem areas were requested, and some
+		 * of them are already initialized but not all?  We expect all shmem
+		 * areas requested by a single callback to form a coherent unit.
+		 */
+		if (found)
 		{
-			/* 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)));
+			if (callbacks->attach_fn)
+				callbacks->attach_fn(callbacks->attach_fn_arg);
+		}
+		else
+		{
+			if (callbacks->init_fn)
+				callbacks->init_fn(callbacks->init_fn_arg);
 		}
-		result->size = size;
-		result->allocated_size = allocated_size;
-		result->location = structPtr;
+
+		LWLockRelease(ShmemIndexLock);
+		shmem_request_state = SRS_DONE;
+
+		return;
 	}
 
-	LWLockRelease(ShmemIndexLock);
+	registered_shmem_callbacks = lappend(registered_shmem_callbacks,
+										 (void *) callbacks);
+}
+
+/*
+ * Call all shmem request callbacks.
+ */
+void
+ShmemCallRequestCallbacks(void)
+{
+	ListCell   *lc;
 
-	Assert(ShmemAddrIsValid(structPtr));
+	Assert(shmem_request_state == SRS_INITIAL);
+	shmem_request_state = SRS_REQUESTING;
 
-	Assert(structPtr == (void *) CACHELINEALIGN(structPtr));
+	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)
+{
+	ShmemStructDesc desc;
+	ShmemStructOpts options = {
+		.name = name,
+		.size = size,
+	};
+	ShmemRequest request = {&desc, &options, SHMEM_KIND_STRUCT};
+
+	Assert(shmem_request_state == SRS_DONE ||
+		   shmem_request_state == SRS_INITIALIZING ||
+		   shmem_request_state == SRS_REQUESTING);
+
+	/* look it up immediately */
+	LWLockAcquire(ShmemIndexLock, LW_EXCLUSIVE);
+	*foundPtr = AttachOrInitShmemIndexEntry(&request, true, true);
+	LWLockRelease(ShmemIndexLock);
+
+	Assert(desc.ptr != NULL);
+	return desc.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..4e5b8a66fde 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 "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(ShmemHashDesc *desc, 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(&desc->base, &options_copy->base, SHMEM_KIND_HASH);
+}
+
+void
+shmem_hash_init(ShmemStructDesc *base_desc, ShmemStructOpts *base_options)
+{
+	ShmemHashDesc *desc = (ShmemHashDesc *) base_desc;
+	ShmemHashOpts *options = (ShmemHashOpts *) base_options;
+	int			hash_flags = options->hash_flags;
+
+	options->hash_info.hctl = desc->base.ptr;
+	Assert(options->hash_info.hctl != NULL);
+	desc->ptr = shmem_hash_create(desc->base.ptr, base_desc->size, false,
+								  desc->base.name,
+								  options->nelems, &options->hash_info, hash_flags);
+
+	if (options->ptr)
+		*options->ptr = desc->ptr;
+}
+
+void
+shmem_hash_attach(ShmemStructDesc *base_desc, ShmemStructOpts *base_options)
+{
+	ShmemHashDesc *desc = (ShmemHashDesc *) base_desc;
+	ShmemHashOpts *options = (ShmemHashOpts *) base_options;
+	int			hash_flags = options->hash_flags;
+
+	/* attach to it rather than allocate and initialize new space */
+	hash_flags |= HASH_ATTACH;
+	options->hash_info.hctl = desc->base.ptr;
+	Assert(options->hash_info.hctl != NULL);
+	desc->ptr = shmem_hash_create(desc->base.ptr, base_desc->size, true,
+								  desc->base.name,
+								  options->nelems, &options->hash_info, hash_flags);
+
+	if (options->ptr)
+		*options->ptr = desc->ptr;
+}
+
 /*
  * 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 */
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..af7cc86d80a 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -4155,7 +4155,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 bbf32523c0b..f57672fbc58 100644
--- a/src/include/storage/shmem.h
+++ b/src/include/storage/shmem.h
@@ -24,43 +24,218 @@
 #include "storage/spin.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 */
+} ShmemAreaKind;
+
+/*
+ * ShmemStructDesc is backend-private handle for a shared memory area
+ * requested with ShmemRequestStruct().
+ */
+typedef struct ShmemStructDesc
+{
+	/* Name and size of the shared memory area. */
+	const char *name;
+
+	void	   *ptr;
+	size_t		size;
+} ShmemStructDesc;
+
+#define SHMEM_ATTACH_UNKNOWN_SIZE (-1)
+
+/*
+ * 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;
+
+	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;
+
+/*
+ * Backend-private handle for a named shared memory hash table, similar to
+ * ShmemStructDesc.
+ */
+typedef struct ShmemHashDesc
+{
+	/*
+	 * Descriptor of the underlying fixed-size allocated area where the hash
+	 * table lives.
+	 */
+	ShmemStructDesc base;
+
+	HTAB	   *ptr;
+} ShmemHashDesc;
+
+/*
+ * 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
 
 /* shmem.c */
 typedef struct PGShmemHeader PGShmemHeader; /* avoid including
 											 * storage/pg_shmem.h here */
+extern void ResetShmemAllocator(void);
 extern void InitShmemAllocator(PGShmemHeader *seghdr);
+#ifdef EXEC_BACKEND
+extern void AttachShmemAllocator(PGShmemHeader *seghdr);
+#endif
 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 void RegisterShmemCallbacks(const ShmemCallbacks *callbacks);
+
+extern void ShmemRequestInternal(ShmemStructDesc *desc, ShmemStructOpts *options,
+								 ShmemAreaKind kind);
+
+/*
+ * These macros provide syntactic sugar for calling the underlying functions
+ * with named arguments -like syntax.
+ */
+#define ShmemRequestStruct(desc, ...)  \
+	ShmemRequestStructWithOpts(desc, &(ShmemStructOpts){__VA_ARGS__})
+
+#define ShmemRequestHash(desc, ...)  \
+	ShmemRequestHashWithOpts(desc, &(ShmemHashOpts){__VA_ARGS__})
+
+extern void ShmemRequestStructWithOpts(ShmemStructDesc *desc, const ShmemStructOpts *options);
+extern void ShmemRequestHashWithOpts(ShmemHashDesc *desc, const ShmemHashOpts *options);
+extern void ShmemCallRequestCallbacks(void);
+
+/* 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 size_t ShmemGetRequestedSize(void);
+extern void ShmemInitRequested(void);
+#ifdef EXEC_BACKEND
+extern void ShmemAttachRequested(void);
+#endif
+
 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)
+							   const char *name, int64 nelems, HASHCTL *infoP, int hash_flags);
+extern void shmem_hash_init(ShmemStructDesc *base_desc, ShmemStructOpts *options);
+extern void shmem_hash_attach(ShmemStructDesc *base_desc, ShmemStructOpts *options);
 
 /* 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/test/modules/test_aio/test_aio.c b/src/test/modules/test_aio/test_aio.c
index d7530681192..34487a05486 100644
--- a/src/test/modules/test_aio/test_aio.c
+++ b/src/test/modules/test_aio/test_aio.c
@@ -77,7 +77,6 @@ static InjIoErrorState *inj_io_error_state;
 static shmem_request_hook_type prev_shmem_request_hook = NULL;
 static shmem_startup_hook_type prev_shmem_startup_hook = NULL;
 
-
 static PgAioHandle *last_handle;
 
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index c72f6c59573..5894893997c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2863,9 +2863,16 @@ SharedTypmodTableEntry
 Sharedsort
 ShellTypeInfo
 ShippableCacheEntry
-ShmemAllocatorData
 ShippableCacheKey
+ShmemAllocatorData
+ShmemAreaKind
+ShmemCallbacks
 ShmemIndexEnt
+ShmemHashDesc
+ShmemHashOpts
+ShmemRequest
+ShmemStructDesc
+ShmemStructOpts
 ShutdownForeignScan_function
 ShutdownInformation
 ShutdownMode
-- 
2.47.3

