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(&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,
+ );
+}
-LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
-ptr = ShmemInitStruct("my struct name", size, &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(&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 initializing 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,
</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.
* 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 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.
+ *
+ * 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"
#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
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;
#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.
* 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);
}
}
+/*
+ * 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
*
}
/*
- * ShmemInitStruct -- Create/attach to a structure in shared memory.
+ * Register callbacks that define a shared memory area (or multiple areas).
*
- * This is called during initialization to find or allocate
- * a data structure in shared memory. If no other process
- * has created the structure, this routine allocates space
- * for it. If it exists already, a pointer to the existing
- * structure is returned.
+ * The system will call the callbacks at different stages of postmaster or
+ * backend startup, to allocate and initialize the area.
*
- * Returns: pointer to the object. *foundPtr is set true if the object was
- * already in the shmem index (hence, already initialized).
+ * This is normally called early during postmaster startup, but if the
+ * SHMEM_CALLBACKS_ALLOW_AFTER_STARTUP is set, this can also be used after
+ * startup, although after startup there's no guarantee that there's enough
+ * shared memory available. When called after startup, this immediately calls
+ * the right callbacks depending on whether another backend had already
+ * initialized the area.
*
- * Note: before Postgres 9.0, this function returned NULL for some failure
- * cases. Now, it always throws error instead, so callers need not check
- * for NULL.
+ * Note: In EXEC_BACKEND mode, this needs to be called in every backend
+ * process. That's needed because we cannot pass down the callback function
+ * pointers from the postmaster process, because different processes may have
+ * loaded libraries to different addresses.
*/
-void *
-ShmemInitStruct(const char *name, Size size, bool *foundPtr)
+void
+RegisterShmemCallbacks(const ShmemCallbacks *callbacks)
{
- ShmemIndexEnt *result;
- void *structPtr;
+ if (shmem_request_state == SRS_DONE && IsUnderPostmaster)
+ {
+ /*
+ * After-startup initialization or attachment. Call the appropriate
+ * callbacks immediately.
+ */
+ if ((callbacks->flags & SHMEM_CALLBACKS_ALLOW_AFTER_STARTUP) == 0)
+ elog(ERROR, "cannot request shared memory at this time");
- Assert(ShmemIndex != NULL);
+ CallShmemCallbacksAfterStartup(callbacks);
+ }
+ else
+ {
+ /* Remember the callbacks for later */
+ registered_shmem_callbacks = lappend(registered_shmem_callbacks,
+ (void *) callbacks);
+ }
+}
+
+/*
+ * Register a shmem area (or multiple areas) after startup.
+ */
+static void
+CallShmemCallbacksAfterStartup(const ShmemCallbacks *callbacks)
+{
+ bool found_any;
+ bool notfound_any;
+
+ Assert(shmem_request_state == SRS_DONE);
+ shmem_request_state = SRS_REQUESTING;
+
+ /*
+ * Call the request callback first. The callback 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);
- /* look it up in the shmem index */
- result = (ShmemIndexEnt *)
- hash_search(ShmemIndex, name, HASH_ENTER_NULL, foundPtr);
+ /*
+ * Check if the requested shared memory areas have already been
+ * initialized. We assume all the areas requested by the request callback
+ * to form a coherent unit such that they're all already initialized or
+ * none. Otherwise it would be ambiguous which callback, init or attach,
+ * to callback afterwards.
+ */
+ found_any = notfound_any = false;
+ foreach_ptr(ShmemRequest, request, pending_shmem_requests)
+ {
+ if (hash_search(ShmemIndex, request->options->name, HASH_FIND, NULL))
+ found_any = true;
+ else
+ notfound_any = true;
+ }
+ if (found_any && notfound_any)
+ elog(ERROR, "found some but not all");
- if (!result)
+ /*
+ * Allocate or attach all the shmem areas requested by the request_fn
+ * callback.
+ */
+ foreach_ptr(ShmemRequest, request, pending_shmem_requests)
{
- LWLockRelease(ShmemIndexLock);
- ereport(ERROR,
- (errcode(ERRCODE_OUT_OF_MEMORY),
- errmsg("could not create ShmemIndex entry for data structure \"%s\"",
- name)));
+ if (found_any)
+ AttachShmemIndexEntry(request, false);
+ else
+ InitShmemIndexEntry(request);
}
+ list_free_deep(pending_shmem_requests);
+ pending_shmem_requests = NIL;
- if (*foundPtr)
+ /* Finish by calling the appropriate subsystem-specific callback */
+ if (found_any)
{
- /*
- * Structure is in the shmem index so someone else has allocated it
- * already. The size better be the same as the size we are trying to
- * initialize to, or there is a name conflict (or worse).
- */
- if (result->size != size)
- {
- LWLockRelease(ShmemIndexLock);
- ereport(ERROR,
- (errmsg("ShmemIndex entry size is wrong for data structure"
- " \"%s\": expected %zu, actual %zu",
- name, size, result->size)));
- }
- structPtr = result->location;
+ if (callbacks->attach_fn)
+ callbacks->attach_fn(callbacks->opaque_arg);
}
else
{
- Size allocated_size;
-
- /* It isn't in the table yet. allocate and initialize it */
- structPtr = ShmemAllocRaw(size, &allocated_size);
- if (structPtr == NULL)
- {
- /* out of memory; remove the failed ShmemIndex entry */
- hash_search(ShmemIndex, name, HASH_REMOVE, NULL);
- LWLockRelease(ShmemIndexLock);
- ereport(ERROR,
- (errcode(ERRCODE_OUT_OF_MEMORY),
- errmsg("not enough shared memory for data structure"
- " \"%s\" (%zu bytes requested)",
- name, size)));
- }
- result->size = size;
- result->allocated_size = allocated_size;
- result->location = structPtr;
+ if (callbacks->init_fn)
+ callbacks->init_fn(callbacks->opaque_arg);
}
LWLockRelease(ShmemIndexLock);
+ shmem_request_state = SRS_DONE;
+}
- Assert(ShmemAddrIsValid(structPtr));
+/*
+ * Call all shmem request callbacks.
+ */
+void
+ShmemCallRequestCallbacks(void)
+{
+ ListCell *lc;
- Assert(structPtr == (void *) CACHELINEALIGN(structPtr));
+ Assert(shmem_request_state == SRS_INITIAL);
+ shmem_request_state = SRS_REQUESTING;
+
+ foreach(lc, registered_shmem_callbacks)
+ {
+ const ShmemCallbacks *callbacks = (const ShmemCallbacks *) lfirst(lc);
- return structPtr;
+ if (callbacks->request_fn)
+ callbacks->request_fn(callbacks->opaque_arg);
+ }
}
+/*
+ * ShmemInitStruct -- Create/attach to a structure in shared memory.
+ *
+ * This is called during initialization to find or allocate
+ * a data structure in shared memory. If no other process
+ * has created the structure, this routine allocates space
+ * for it. If it exists already, a pointer to the existing
+ * structure is returned.
+ *
+ * Returns: pointer to the object. *foundPtr is set true if the object was
+ * already in the shmem index (hence, already initialized).
+ *
+ * Note: This is a legacy interface, kept for backwards compatibility with
+ * extensions. Use ShmemRequestStruct() in new code!
+ */
+void *
+ShmemInitStruct(const char *name, Size size, bool *foundPtr)
+{
+ void *ptr = NULL;
+ ShmemStructOpts options = {
+ .name = name,
+ .size = size,
+ .ptr = &ptr,
+ };
+ ShmemRequest request = {&options, SHMEM_KIND_STRUCT};
+
+ Assert(shmem_request_state == SRS_DONE ||
+ shmem_request_state == SRS_INITIALIZING ||
+ shmem_request_state == SRS_REQUESTING);
+
+ LWLockAcquire(ShmemIndexLock, LW_EXCLUSIVE);
+
+ /*
+ * During postmaster startup, look up the existing entry if any.
+ */
+ *foundPtr = false;
+ if (IsUnderPostmaster)
+ *foundPtr = AttachShmemIndexEntry(&request, true);
+
+ /* Initialize it if not found */
+ if (!*foundPtr)
+ InitShmemIndexEntry(&request);
+
+ LWLockRelease(ShmemIndexLock);
+
+ Assert(ptr != NULL);
+ return ptr;
+}
/*
* Add two Size values, checking for overflow