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
Anonymous allocations are allocations that have been made
with ShmemAlloc() directly, rather than via
- ShmemInitStruct() or
- ShmemInitHash().
+ ShmemRequestStruct() or
+ ShmemRequestHash().
diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 70e815b8a2c..789cac9fcab 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -3628,71 +3628,128 @@ 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
shared_preload_libraries.
- The shared library should also register a
- shmem_request_hook in its
- _PG_init function. This
- shmem_request_hook can reserve shared memory by
- calling:
+ The shared library should register callbacks in
+ its _PG_init function, which then get called at the
+ right stages of the system startup to initialize the shared memory.
+ Here is an example:
-void RequestAddinShmemSpace(Size size)
-
- Each backend should obtain a pointer to the reserved shared memory by
- calling:
-
-void *ShmemInitStruct(const char *name, Size size, bool *foundPtr)
-
- If this function sets foundPtr to
- false, the caller should proceed to initialize the
- contents of the reserved shared memory. If foundPtr
- is set to true, the shared memory was already
- initialized by another backend, and the caller need not initialize
- further.
-
+typedef struct MyShmemData {
+ LWLock lock; /* protects the fields below */
-
- To avoid race conditions, each backend should use the LWLock
- AddinShmemInitLock when initializing its allocation
- of shared memory, as shown here:
-
-static mystruct *ptr = NULL;
-bool found;
+ ... shared memory contents ...
+} MyShmemData;
-LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
-ptr = ShmemInitStruct("my struct name", size, &found);
-if (!found)
+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)
{
- ... initialize contents of shared memory ...
- ptr->locks = GetNamedLWLockTranche("my tranche name");
-}
-LWLockRelease(AddinShmemInitLock);
-
- shmem_startup_hook 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
- EXEC_BACKEND is defined), each backend executes the
- registered shmem_startup_hook shortly after it
- attaches to shared memory, so add-ins should still acquire
- AddinShmemInitLock within this hook, as shown in the
- example above. On other platforms, only the postmaster process executes
- the shmem_startup_hook, and each backend automatically
- inherits the pointers to shared memory.
-
+ /*
+ * 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(&my_shmem_callbacks);
+}
+
+/* callback to request shmem space */
+static void
+my_shmem_request(void *arg)
+{
+ ShmemRequestStruct(.name = "My shmem area",
+ .size = sizeof(MyShmemData),
+ .ptr = (void **) &MyShmem,
+ );
+}
+
+/* callback to initialize the contents of the MyShmem area at startup */
+static void
+my_shmem_init(void *arg)
+{
+ int tranche_id;
+
+ /* Initialize the lock */
+ tranche_id = LWLockNewTrancheId("my tranche name");
+ LWLockInitialize(&MyShmem->lock, tranche_id);
+
+ ... initialize the rest of MyShmem fields ...
+}
+
+
+ The request_fn callback is called during system
+ startup, before the shared memory has been allocated. It should call
+ ShmemRequestStruct() to register the add-in's
+ shared memory needs. Note that ShmemRequestStruct()
+ 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 init_fn() 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.
+
- An example of a shmem_request_hook and
- shmem_startup_hook can be found in
+ On Windows, the attach_fn 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 fork() on other systems.
+
+
+ An example of allocating shared memory can be found in
contrib/pg_stat_statements/pg_stat_statements.c in
the PostgreSQL source tree.
- Requesting Shared Memory After Startup
+ Requesting Shared Memory After Startup with ShmemRequestStruct
+
+
+ The ShmemRequestStruct() can also be called after
+ system startup, which is useful to allow small allocations in add-in
+ libraries that are not specified in
+ shared_preload_libraries.
+ 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.
+
+
+ By default, RegisterShmemCallbacks() fails with an
+ error if called after system startup. To use it after startup, you must
+ set the SHMEM_CALLBACKS_ALLOW_AFTER_STARTUP flag in
+ the argument ShmemCallbacks struct to
+ acknowledge the risk.
+
+
+ When RegisterShmemCallbacks() 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 initializing the
+ memory area concurrently.
+
+
+
+
+ Allocating Dynamic Shared Memory After Startup
There is another, more flexible method of reserving shared memory that
- can be done after server startup and outside a
- shmem_request_hook. 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:
void *GetNamedDSMSegment(const char *name, size_t size,
@@ -3711,10 +3768,7 @@ void *GetNamedDSMSegment(const char *name, size_t size,
- Unlike shared memory reserved at server startup, there is no need to
- acquire AddinShmemInitLock or otherwise take action
- to avoid race conditions when reserving shared memory with
- GetNamedDSMSegment. This function ensures that only
+ GetNamedDSMSegment 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 ebd41176b94..3766d8231ac 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 15b136ea29d..0973010b7dc 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 "storage/shmem_internal.h"
#include "tcop/backend_startup.h"
@@ -673,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..7a8ee19bdaf 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've 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 ca4e4727489..24422a80ab3 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -101,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());
@@ -177,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();
/*
@@ -221,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. */
@@ -252,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 e91c47d4d97..e1724b0358d 100644
--- a/src/backend/storage/ipc/shmem.c
+++ b/src/backend/storage/ipc/shmem.c
@@ -19,43 +19,109 @@
* 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).
+ * 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.
*
- * (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.
+ * 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.
*
- * (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.
+ * Nowadays, there is also another 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.
*
- * (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.
+ * 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.
+ *
+ * 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,
+ * call ShmemRequestStruct() with the desired name and size. When the area is
+ * later allocated or attached to, the global variable pointed to by the .ptr
+ * option is set to the shared memory location of the allocation. The init_fn
+ * callback can perform additional initialization.
+ *
+ * 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)
+ * {
+ * ShmemRequestStruct(.name = "My shmem area",
+ * .size = sizeof(MyShmemData),
+ * .ptr = (void **) &MyShmem,
+ * );
+ * }
+ *
+ * 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
+ * (*options->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"
@@ -75,6 +141,75 @@
#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
@@ -106,25 +241,380 @@ 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);
+/*
+ * 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 registers 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: '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 ShmemHashOpts 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 looks valid, remember it */
+ 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 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->opaque_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->opaque_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.
+ */
+static void
+InitShmemIndexEntry(ShmemRequest *request)
+{
+ 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;
@@ -133,6 +623,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.
@@ -177,19 +677,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);
@@ -210,6 +712,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
*
@@ -306,6 +825,144 @@ ShmemAddrIsValid(const void *addr)
return (addr >= ShmemBase) && (addr < ShmemEnd);
}
+/*
+ * Register callbacks that define a shared memory area (or multiple areas).
+ *
+ * The system will call the callbacks at different stages of postmaster or
+ * backend startup, to allocate and initialize the area.
+ *
+ * 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: 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
+RegisterShmemCallbacks(const ShmemCallbacks *callbacks)
+{
+ if (shmem_request_state == SRS_DONE && IsUnderPostmaster)
+ {
+ /*
+ * After-startup initialization or attachment. Call the appropriate
+ * callbacks immediately.
+ */
+ if ((callbacks->flags & SHMEM_CALLBACKS_ALLOW_AFTER_STARTUP) == 0)
+ elog(ERROR, "cannot request shared memory at this time");
+
+ 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 makes 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->opaque_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);
+
+ /*
+ * 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");
+
+ /*
+ * Allocate or attach all the shmem areas requested by the request_fn
+ * callback.
+ */
+ foreach_ptr(ShmemRequest, request, pending_shmem_requests)
+ {
+ if (found_any)
+ AttachShmemIndexEntry(request, false);
+ else
+ InitShmemIndexEntry(request);
+ }
+ list_free_deep(pending_shmem_requests);
+ pending_shmem_requests = NIL;
+
+ /* Finish by calling the appropriate subsystem-specific callback */
+ if (found_any)
+ {
+ if (callbacks->attach_fn)
+ callbacks->attach_fn(callbacks->opaque_arg);
+ }
+ else
+ {
+ if (callbacks->init_fn)
+ callbacks->init_fn(callbacks->opaque_arg);
+ }
+
+ LWLockRelease(ShmemIndexLock);
+ shmem_request_state = SRS_DONE;
+}
+
+/*
+ * Call all shmem request callbacks.
+ */
+void
+ShmemCallRequestCallbacks(void)
+{
+ ListCell *lc;
+
+ Assert(shmem_request_state == SRS_INITIAL);
+ shmem_request_state = SRS_REQUESTING;
+
+ foreach(lc, registered_shmem_callbacks)
+ {
+ const ShmemCallbacks *callbacks = (const ShmemCallbacks *) lfirst(lc);
+
+ if (callbacks->request_fn)
+ callbacks->request_fn(callbacks->opaque_arg);
+ }
+}
+
/*
* ShmemInitStruct -- Create/attach to a structure in shared memory.
*
@@ -318,82 +975,43 @@ ShmemAddrIsValid(const void *addr)
* Returns: pointer to the object. *foundPtr is set true if the object was
* already in the shmem index (hence, already initialized).
*
- * 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 ShmemRequestStruct() in new code!
*/
void *
ShmemInitStruct(const char *name, Size size, bool *foundPtr)
{
- ShmemIndexEnt *result;
- void *structPtr;
+ void *ptr = NULL;
+ ShmemStructOpts options = {
+ .name = name,
+ .size = size,
+ .ptr = &ptr,
+ };
+ ShmemRequest request = {&options, SHMEM_KIND_STRUCT};
- Assert(ShmemIndex != NULL);
+ Assert(shmem_request_state == SRS_DONE ||
+ shmem_request_state == SRS_INITIALIZING ||
+ shmem_request_state == SRS_REQUESTING);
LWLockAcquire(ShmemIndexLock, LW_EXCLUSIVE);
- /* look it up in the shmem index */
- result = (ShmemIndexEnt *)
- hash_search(ShmemIndex, name, HASH_ENTER_NULL, foundPtr);
+ /*
+ * During postmaster startup, look up the existing entry if any.
+ */
+ *foundPtr = false;
+ if (IsUnderPostmaster)
+ *foundPtr = AttachShmemIndexEntry(&request, true);
- if (!result)
- {
- LWLockRelease(ShmemIndexLock);
- ereport(ERROR,
- (errcode(ERRCODE_OUT_OF_MEMORY),
- errmsg("could not create ShmemIndex entry for data structure \"%s\"",
- name)));
- }
-
- 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).
- */
- 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;
- }
- 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;
- }
+ /* Initialize it if not found */
+ if (!*foundPtr)
+ InitShmemIndexEntry(&request);
LWLockRelease(ShmemIndexLock);
- Assert(ShmemAddrIsValid(structPtr));
-
- Assert(structPtr == (void *) CACHELINEALIGN(structPtr));
-
- return structPtr;
+ 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 1ca4a321c0f..c28d673cbd2 100644
--- a/src/backend/storage/ipc/shmem_hash.c
+++ b/src/backend/storage/ipc/shmem_hash.c
@@ -20,6 +20,7 @@
#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
@@ -33,6 +34,66 @@ typedef struct 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.
@@ -49,9 +110,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 */
@@ -65,7 +125,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,
@@ -75,8 +142,8 @@ ShmemInitHash(const char *name, /* table string name for shmem index */
/*
* Initialize or attach to a shared hash table in the given shmem region.
*
- * This is extracted from ShmemInitHash() to allow InitShmemAllocator() to
- * share the logic for bootstrapping the ShmemIndex hash table.
+ * This is exposed to allow InitShmemAllocator() to share the logic for
+ * bootstrapping the ShmemIndex hash table.
*/
HTAB *
shmem_hash_create(void *location, size_t size, bool 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/backend/utils/hash/dynahash.c b/src/backend/utils/hash/dynahash.c
index d49a7a92c64..cb95ad4ef2a 100644
--- a/src/backend/utils/hash/dynahash.c
+++ b/src/backend/utils/hash/dynahash.c
@@ -119,8 +119,8 @@
* chosen at creation based on the initial number of elements, so even though
* we support allocating more elements later, performance will suffer if the
* table grows much beyond the initial size. (Currently, shared memory hash
- * tables are only created by ShmemInitHash() though, which doesn't support
- * growing at all.)
+ * tables are only created by ShmemRequestHash()/ShmemInitHash() though, which
+ * doesn't support growing at all.)
*/
#define HASH_SEGSIZE 256
#define HASH_SEGSIZE_SHIFT 8 /* must be log2(HASH_SEGSIZE) */
diff --git a/src/include/storage/shmem.h b/src/include/storage/shmem.h
index 81d05381f8f..1680712498f 100644
--- a/src/include/storage/shmem.h
+++ b/src/include/storage/shmem.h
@@ -28,21 +28,166 @@
#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 */
+ /*
+ * 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 a contiguous shmem area.
+ */
+typedef struct ShmemHashOpts
+{
+ /* Options for allocating the underlying shmem area; do not touch directly */
+ 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 *opaque_arg);
+typedef void (*ShmemInitCallback) (void *opaque_arg);
+typedef void (*ShmemAttachCallback) (void *opaque_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
+ * ShmemRequestStruct() and ShmemRequestHash() to register the subsystem's
+ * shared memory needs.
+ */
+ ShmemRequestCallback request_fn;
+
+ /*
+ * Initialization callback function. This is called after the shared
+ * memory area has been allocated, usually at postmaster startup.
+ */
+ ShmemInitCallback init_fn;
+
+ /*
+ * 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;
+
+ /*
+ * Argument passed to the callbacks. This is opaque to the shmem system,
+ * callbacks can use it for their own purposes.
+ */
+ void *opaque_arg;
+} ShmemCallbacks;
+
+/*
+ * Flags to control the behavior of RegisterShmemCallbacks().
+ *
+ * SHMEM_CALLBACKS_ALLOW_AFTER_STARTUP: Normally, calling
+ * RegisterShmemCallbacks() after postmaster startup, e.g. in an add-in
+ * library loaded on-demand in a backend, results in an error, because shared
+ * memory should generally be requested at postmaster startup time. But if
+ * this flag is set, it is allowed and the callbacks are called immediately 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 bool ShmemAddrIsValid(const void *addr);
-extern void *ShmemInitStruct(const char *name, Size size, bool *foundPtr);
+
extern Size add_size(Size s1, Size s2);
extern Size mul_size(Size s1, Size s2);
extern PGDLLIMPORT Size pg_get_shmem_pagesize(void);
-/* shmem_hash.c */
-extern HTAB *ShmemInitHash(const char *name, int64 nelems,
- HASHCTL *infoP, int hash_flags);
-
/* ipci.c */
extern void RequestAddinShmemSpace(Size size);
diff --git a/src/include/storage/shmem_internal.h b/src/include/storage/shmem_internal.h
index e0638135639..9064b86b9a1 100644
--- a/src/include/storage/shmem_internal.h
+++ b/src/include/storage/shmem_internal.h
@@ -16,26 +16,37 @@
#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);
-
-/* 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;
+extern void shmem_hash_init(void *location, ShmemStructOpts *base_options);
+extern void shmem_hash_attach(void *location, ShmemStructOpts *base_options);
#endif /* SHMEM_INTERNAL_H */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index ca0c86d9e59..8eea59b62fc 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2866,9 +2866,14 @@ SharedTypmodTableEntry
Sharedsort
ShellTypeInfo
ShippableCacheEntry
-ShmemAllocatorData
ShippableCacheKey
+ShmemAllocatorData
+ShmemCallbacks
ShmemIndexEnt
+ShmemHashOpts
+ShmemRequest
+ShmemRequestKind
+ShmemStructOpts
ShutdownForeignScan_function
ShutdownInformation
ShutdownMode