From 3701db9df2d08a3995974d7447edb1ca59bff37f Mon Sep 17 00:00:00 2001 From: Heikki Linnakangas Date: Fri, 27 Mar 2026 02:32:36 +0200 Subject: [PATCH v8 06/16] Introduce a new mechanism for registering shared memory areas Each shared memory area is registered with a "descriptor struct" that contains parameters like name and size of the area. The descriptor struct makes it easier to add optional fields in the future; the additional fields can just be left as zeros. This merges the separate [Subsystem]ShmemSize() and [Subsystem]ShmemInit() phases at postmaster startup. Each subsystem is now called into just once, before the shared memory segment has been allocated, to register the subsystem's shared memory needs. The registration includes the size, which replaces the [Subsystem]ShmemSize() calls, and a pointer to an initialization callback function, which replaces the [Subsystem]ShmemInit() calls. This is more ergonomic, as you only need to calculate the size once, when you register the struct. This replaces ShmemInitStruct() and ShmemInitHash(), which become just backwards-compatibility wrappers around the new functions. In future commits, I plan to replace all ShmemInitStruct() and ShmemInitHash() calls with the new functions, although we'll still need to keep them around for extensions. Reviewed-by: Ashutosh Bapat Reviewed-by: Zsolt Parragi Reviewed-by: Robert Haas Reviewed-by: Daniel Gustafsson Discussion: https://www.postgresql.org/message-id/CAExHW5vM1bneLYfg0wGeAa=52UiJ3z4vKd3AJ72X8Fw6k3KKrg@mail.gmail.com --- doc/src/sgml/system-views.sgml | 4 +- doc/src/sgml/xfunc.sgml | 161 +++-- src/backend/bootstrap/bootstrap.c | 1 + src/backend/postmaster/launch_backend.c | 4 + src/backend/postmaster/postmaster.c | 18 +- src/backend/storage/ipc/ipci.c | 33 +- src/backend/storage/ipc/shmem.c | 770 ++++++++++++++++++++---- src/backend/storage/ipc/shmem_hash.c | 92 ++- src/backend/storage/lmgr/proc.c | 3 + src/backend/tcop/postgres.c | 9 +- src/backend/utils/hash/dynahash.c | 2 + src/include/storage/shmem.h | 210 ++++++- src/test/modules/test_aio/test_aio.c | 1 - src/tools/pgindent/typedefs.list | 9 +- 14 files changed, 1120 insertions(+), 197 deletions(-) diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml index 9ee1a2bfc6a..2ebec6928d5 100644 --- a/doc/src/sgml/system-views.sgml +++ b/doc/src/sgml/system-views.sgml @@ -4254,8 +4254,8 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx 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..1e9ff801a7c 100644 --- a/doc/src/sgml/xfunc.sgml +++ b/doc/src/sgml/xfunc.sgml @@ -3628,71 +3628,131 @@ 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 + 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; + +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 = { + .shmem_request_fn = my_shmem_request, + .shmem_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 */ +static void +my_shmem_request(void *arg) +{ + /* A persistent handle to the shared memory area in this backend */ + static ShmemStructDesc MyShmemDesc; + + ShmemRequestStruct(&MyShmemDesc, &(ShmemRequestStructOpts) { + .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); + - 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. + 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 <function>ShmemRequestStruct</function> + + + 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_ALLOW_AFTER_STARTUP flag in the + descriptor 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 initializating 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 +3771,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 68a42de0889..86fe86354f5 100644 --- a/src/backend/bootstrap/bootstrap.c +++ b/src/backend/bootstrap/bootstrap.c @@ -370,6 +370,7 @@ BootstrapModeMain(int argc, char *argv[], bool check_only) InitializeFastPathLocks(); + ShmemCallRequestCallbacks(); CreateSharedMemoryAndSemaphores(); /* diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c index 434e0643022..75423104be8 100644 --- a/src/backend/postmaster/launch_backend.c +++ b/src/backend/postmaster/launch_backend.c @@ -49,6 +49,7 @@ #include "replication/walreceiver.h" #include "storage/dsm.h" #include "storage/io_worker.h" +#include "storage/ipc.h" #include "storage/pg_shmem.h" #include "tcop/backend_startup.h" #include "utils/memutils.h" @@ -672,7 +673,10 @@ SubPostmasterMain(int argc, char *argv[]) /* Restore basic shared memory pointers */ if (UsedShmemSegAddr != NULL) + { InitShmemAllocator(UsedShmemSegAddr); + ShmemCallRequestCallbacks(); + } /* * Run the appropriate Main function diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 3fac46c402b..e81ef248bf1 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -959,7 +959,14 @@ PostmasterMain(int argc, char *argv[]) InitializeFastPathLocks(); /* - * Give preloaded libraries a chance to request additional shared memory. + * Ask all subsystems, including preloaded libraries, to register their + * shared memory needs. + */ + ShmemCallRequestCallbacks(); + + /* + * Also call any legacy shmem request hooks that might'be been installed + * by preloaded libraries. */ process_shmem_requests(); @@ -3235,7 +3242,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 + * RegisterShmemStructs() here, 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 d692d419846..493ddd7f12f 100644 --- a/src/backend/storage/ipc/ipci.c +++ b/src/backend/storage/ipc/ipci.c @@ -99,8 +99,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()); @@ -174,6 +175,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(); /* @@ -218,7 +226,21 @@ CreateSharedMemoryAndSemaphores(void) */ InitShmemAllocator(seghdr); - /* Initialize subsystems */ + /* Reserve space for semaphores. */ + if (!IsUnderPostmaster) + PGReserveSemaphores(ProcGlobalSemas()); + + /* + * 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. */ @@ -249,11 +271,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 c86d691dcfb..84099ce78fe 100644 --- a/src/backend/storage/ipc/shmem.c +++ b/src/backend/storage/ipc/shmem.c @@ -19,48 +19,116 @@ * 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, but - * their actual size can vary dynamically. When entries are added - * to the table, more space is allocated. 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. However, - * if one hash table grows very large and then shrinks, its space - * cannot be redistributed to other tables. We could build a simple - * hash bucket garbage collector if need be. Right now, it seems - * unnecessary. + * 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 in the + * descriptor 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 loaded after startup. Despite that allowance, + * extensions that need shared memory should be added in + * shared_preload_libraries, because the allowance is quite small and there is + * no guarantee that any memory is available after startup. + * + * Nowadays, there is also a third way to allocate shared memory called + * Dynamic Shared Memory. See dsm.c for that facility. One big difference + * between traditional shared memory handled by shmem.c and dynamic shared + * memory is that traditional shared memory areas are mapped to the same + * address in all processes, so you can use normal pointers in shared memory + * structs. With Dynamic Shared Memory, you must use offsets or DSA pointers + * instead. + * + * Shared memory managed by shmem.c can never be freed, once allocated. Each + * hash table has its own free list, so hash buckets can be reused when an + * item is deleted. However, if one hash table grows very large and then + * shrinks, its space cannot be redistributed to other tables. We could build + * a simple hash bucket garbage collector if need be. Right now, it seems + * unnecessary. + * + * Usage + * ----- + * + * To allocate shared memory, you need to register a set of callback functions + * which handle the lifecycle of the allocation. In the register_fn + * callback, fill in a ShmemStructDesc descriptor with the name, size, and any + * other options, and call ShmemRequestStruct(). Leave any unused fields as + * zeros. + * + * typedef struct MyShmemData { + * ... + * } MyShmemData; + * + * static MyShmemData *MyShmem; + * + * static void my_shmem_request(void *arg); + * static void my_shmem_init(void *arg); + * + * const ShmemCallbacks MyShmemCallbacks = { + * .request_fn = my_shmem_request, + * .init_fn = my_shmem_init, + * }; + * + * static void + * my_shmem_request(void *arg) + * { + * static ShmemStructDesc MyShmemDesc; + * + * ShmemRequestStruct(&MyShmemDesc, &(ShmemRequestStructOpts) { + * .name = "My shmem area", + * .size = sizeof(MyShmemData), + * .ptr = (void **) &MyShmem, + * }); + * } + * + * In builtin PostgreSQL code, add the callbacks to the list in + * src/include/storage/subsystemlist.h. In an add-in module, you can register + * the callbacks by calling RegisterShmemCallbacks(&MyShmemCallbacks) in the + * extension's _PG_init() function. + * + * Lifecycle + * --------- + * + * Initializing shared memory happens in multiple phases. In the first phase, + * during postmaster startup, all the shmem_request callbacks are called. + * Only after all the request callbacks have been called and all the shmem + * areas have been requested by the ShmemRequestStruct() calls we know how + * much shared memory we need in total. After that, postmaster allocates + * global shared memory segment, and calls all the init_fn callbacks to + * initialize all the requested shmem areas. + * + * In standard Unix-ish environments, individual backends do not need to + * re-establish their local pointers into shared memory, because they inherit + * correct values of those variables via fork() from the postmaster. However, + * this does not work in the EXEC_BACKEND case. In ports using EXEC_BACKEND, + * backend startup also calls the shmem_request callbacks to re-establish the + * knowledge about each shared memory area, sets the pointer variables + * (*ShmemStructDesc->ptr), and calls the attach_fn callback, if any, for + * additional per-backend setup. + * + * Legacy ShmemInitStruct()/ShmemInitHash() functions + * -------------------------------------------------- + * + * ShmemInitStruct()/ShmemInitHash() is another way of registering shmem + * areas. It pre-dates the ShmemRequestStruct()/ShmemRequestHash() functions, + * and should not be used in new code, but as of this writing it is still + * widely used in extensions. + * + * To allocate a shmem area with ShmemInitStruct(), you need to separately + * register the size needed for the area by calling RequestAddinShmemSpace() + * from the extension's shmem_request_hook, and allocate the area by calling + * ShmemInitStruct() from the extension's shmem_startup_hook. There are no + * init/attach callbacks. Instead, the caller of ShmemInitStruct() must check + * the return status of ShmemInitStruct() and initialize the struct if it was + * not previously initialized. + * + * Calling ShmemAlloc() directly + * ----------------------------- + * + * There's a more low-level way of allocating shared memory too: you can call + * ShmemAlloc() directly. It's used to implement the higher level mechanisms, + * and should generally not be called directly. */ #include "postgres.h" @@ -79,6 +147,74 @@ #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 + * ShmemRequestInternal() are accumulated here. + */ +typedef struct +{ + ShmemStructDesc *desc; + ShmemRequestStructOpts *options; + ShmemAreaKind kind; +} ShmemRequest; + +static List *requested_shmem_areas; + +/* + * 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 -> LATE_ATTACH_OR_INIT -> DONE + */ +static enum +{ + /* Initial state */ + SB_INITIAL, + + /* + * When we call the shmem_request callbacks, we enter the SB_REQUESTING + * phase. All ShmemRequestStruct calls happen in this state. + */ + SB_REQUESTING, + + /* + * Postmaster has finished all shmem requests, and is now initializing the + * shared memory segment. We are now calling the init_fn callbacks. + */ + SB_INITIALIZING, + + /* + * A postmaster child process is starting up. The attach_fn callbacks are + * called in this state. + */ + SB_ATTACHING, + + /* An after-startup allocation or attachment is in progress. */ + SB_LATE_ATTACH_OR_INIT, + + /* Normal state after shmem initialization / attachment */ + SB_DONE, +} shmem_startup_state; + /* * This is the first data structure stored in the shared memory segment, at * the offset that PGShmemHeader->content_offset points to. Allocations by @@ -109,25 +245,373 @@ static void *ShmemBase; /* start address of shared memory */ static void *ShmemEnd; /* end+1 address of shared memory */ static ShmemAllocatorData *ShmemAllocator; -static HTAB *ShmemIndex = NULL; /* primary index hashtable for shmem */ + +/* + * ShmemIndex is a global directory of shmem areas, itself also stored in the + * shared memory. + */ +static HTAB *ShmemIndex; + + /* max size of data structure string name */ +#define SHMEM_INDEX_KEYSIZE (48) + +/* + * # of additional entries to reserve in the shmem index table, for + * allocations after postmaster startup. (This is not a hard limit, the hash + * table can grow larger than that if there is shared memory available) + */ +#define SHMEM_INDEX_ADDITIONAL_SIZE (64) + +/* this is a hash bucket in the shmem index table */ +typedef struct +{ + char key[SHMEM_INDEX_KEYSIZE]; /* string name */ + void *location; /* location in shared mem */ + Size size; /* # bytes requested for the structure */ + Size allocated_size; /* # bytes actually allocated */ +} ShmemIndexEnt; /* To get reliable results for NUMA inquiry we need to "touch pages" once */ static bool firstNumaTouch = true; +static bool AttachOrInit(ShmemRequest *request, bool init_allowed, bool attach_allowed); + 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 register the need for it. + * The actual allocation happens later in the postmaster startup sequence. + * + * This must be called from a shmem_request callback function, registered with + * RegisterShmemCallbacks(). This enforces a coding pattern that works the + * same in normal Unix systems and with EXEC_BACKEND. In postmaster, the the + * shmem_request callback is called during startup, but in EXEC_BACKEND mode, + * it is also called in each backend at backend startup. 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 +ShmemRequestStruct(ShmemStructDesc *desc, const ShmemRequestStructOpts *options) +{ + ShmemRequestStructOpts *options_copy; + + options_copy = MemoryContextAlloc(TopMemoryContext, + sizeof(ShmemRequestStructOpts)); + memcpy(options_copy, options, sizeof(ShmemRequestStructOpts)); + + ShmemRequestInternal(desc, options_copy, SHMEM_KIND_STRUCT); +} + +/* + * Internal workhorse of ShmemRequestStruct() and ShmemRequestHash(). + * + * Note: 'desc' and 'options' must live until the init/attach callbacks have + * been called. Unlike in the public ShmemRequestStruct() and + * ShmemRequestHash() functions, 'options' is *not* copied. This allows + * ShmemRequestHash() to pass a pointer to the extended ShmemRequestHashOpts + * struct instead. + */ +void +ShmemRequestInternal(ShmemStructDesc *desc, ShmemRequestStructOpts *options, + ShmemAreaKind kind) +{ + ListCell *lc; + ShmemRequest *request; + + if (options->name == NULL) + elog(ERROR, "shared memory request is missing 'name' option"); + + if (IsUnderPostmaster) + { + if (options->size <= 0 && options->size != SHMEM_REQUEST_UNKNOWN_SIZE) + elog(ERROR, "invalid size %zd for shared memory request for \"%s\"", + options->size, options->name); + } + else + { + if (options->size == SHMEM_REQUEST_UNKNOWN_SIZE) + elog(ERROR, "SHMEM_REQUEST_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_startup_state != SB_REQUESTING) + elog(ERROR, "ShmemRequestStruct can only be called from a shmem_request callback"); + + /* Check that it's not already registered in this process */ + foreach(lc, requested_shmem_areas) + { + ShmemStructDesc *existing = (ShmemStructDesc *) lfirst(lc); + + if (strcmp(existing->name, options->name) == 0) + ereport(ERROR, + (errmsg("shared memory struct \"%s\" is already registered", + options->name))); + } + + request = palloc(sizeof(ShmemRequest)); + request->options = options; + request->desc = desc; + request->kind = kind; + requested_shmem_areas = lappend(requested_shmem_areas, request); +} + +/* + * ShmemGetRequestedSize() --- estimate the total size of all registered shared + * memory structures. + * + * This is called once at postmaster startup, before the shared memory segment + * has been created. + */ +size_t +ShmemGetRequestedSize(void) +{ + ListCell *lc; + size_t size; + + /* memory needed for the ShmemIndex */ + size = hash_estimate_size(list_length(requested_shmem_areas) + SHMEM_INDEX_ADDITIONAL_SIZE, + sizeof(ShmemIndexEnt)); + + /* memory needed for all the requested areas */ + foreach(lc, requested_shmem_areas) + { + ShmemRequest *request = (ShmemRequest *) lfirst(lc); + + size = add_size(size, request->options->size); + size = add_size(size, request->options->extra_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) +{ + ListCell *lc; + + /* Should be called only by the postmaster or a standalone backend. */ + Assert(!IsUnderPostmaster); + Assert(shmem_startup_state == SB_INITIALIZING); + + /* + * Initialize all the requested memory areas. There are no concurrent + * processes yet, so no need for locking. + */ + foreach(lc, requested_shmem_areas) + { + ShmemRequest *request = (ShmemRequest *) lfirst(lc); + + AttachOrInit(request, true, false); + } + list_free_deep(requested_shmem_areas); + requested_shmem_areas = NIL; + + /* Call init callbacks */ + foreach(lc, registered_shmem_callbacks) + { + const ShmemCallbacks *callbacks = (const ShmemCallbacks *) lfirst(lc); + + if (callbacks->init_fn) + callbacks->init_fn(callbacks->init_fn_arg); + } + + shmem_startup_state = SB_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_startup_state == SB_REQUESTING); + shmem_startup_state = SB_ATTACHING; + + LWLockAcquire(ShmemIndexLock, LW_SHARED); + + /* + * Attach to all the requested memory areas. + */ + foreach(lc, requested_shmem_areas) + { + ShmemRequest *request = (ShmemRequest *) lfirst(lc); + + AttachOrInit(request, false, true); + } + list_free(requested_shmem_areas); + requested_shmem_areas = NIL; + + /* Call attach callbacks */ + foreach(lc, registered_shmem_callbacks) + { + const ShmemCallbacks *callbacks = (const ShmemCallbacks *) lfirst(lc); + + if (callbacks->attach_fn) + callbacks->attach_fn(callbacks->attach_fn_arg); + } + + LWLockRelease(ShmemIndexLock); + + shmem_startup_state = SB_DONE; +} +#endif + +/* + * Workhorse to insert or look up a named shmem area in the shared memory + * index, and initialize or attach to it. + * + * If !init_allowed and the entry is not found, throws an error. If + * !attach_allowed and the entry is found, throws an error. + */ +static bool +AttachOrInit(ShmemRequest *request, bool init_allowed, bool attach_allowed) +{ + /* + * If called after postmaster startup, we need to immediately also + * initialize or attach to the area. + */ + ShmemStructDesc *desc = request->desc; + ShmemIndexEnt *index_entry; + bool found; + + desc->name = request->options->name; + desc->ptr = NULL; + + /* look it up in the shmem index */ + index_entry = (ShmemIndexEnt *) + hash_search(ShmemIndex, request->options->name, + init_allowed ? HASH_ENTER_NULL : HASH_FIND, &found); + if (found) + { + /* Already present, just attach to it */ + if (!attach_allowed) + elog(ERROR, "shared memory struct \"%s\" is already initialized", desc->name); + + if (index_entry->size != request->options->size && + request->options->size != SHMEM_REQUEST_UNKNOWN_SIZE) + { + elog(ERROR, "shared memory struct \"%s\" is already registered with different size", + desc->name); + } + desc->ptr = index_entry->location; + desc->size = index_entry->size; + switch (request->kind) + { + case SHMEM_KIND_STRUCT: + if (request->options->ptr) + *(request->options->ptr) = index_entry->location; + break; + case SHMEM_KIND_HASH: + shmem_hash_attach(desc, request->options); + break; + } + } + else if (!init_allowed) + { + /* attach was requested, but it was not found */ + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("could not find ShmemIndex entry for data structure \"%s\"", + desc->name))); + } + else if (!index_entry) + { + /* tried to add it to the hash table, but there was no space */ + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("could not create ShmemIndex entry for data structure \"%s\"", + desc->name))); + } + else + { + /* + * We inserted the entry to the shared memory index. Allocate + * requested amount of shared memory for it, and do basic + * initializion. + */ + size_t allocated_size; + void *structPtr; + + structPtr = ShmemAllocRaw(request->options->size, &allocated_size); + if (structPtr == NULL) + { + /* out of memory; remove the failed ShmemIndex entry */ + hash_search(ShmemIndex, desc->name, HASH_REMOVE, NULL); + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("not enough shared memory for data structure" + " \"%s\" (%zu bytes requested)", + desc->name, request->options->size))); + } + index_entry->size = request->options->size; + index_entry->allocated_size = allocated_size; + index_entry->location = structPtr; + + desc->ptr = index_entry->location; + desc->size = index_entry->size; + switch (request->kind) + { + case SHMEM_KIND_STRUCT: + if (request->options->ptr) + *(request->options->ptr) = index_entry->location; + break; + case SHMEM_KIND_HASH: + shmem_hash_init(desc, request->options); + break; + } + } + + return found; +} + /* * InitShmemAllocator() --- set up basic pointers to shared memory. * * Called at postmaster or stand-alone backend startup, to initialize the * allocator's data structure in the shared memory segment. In EXEC_BACKEND, - * this is also called at backend startup, to set up pointers to the shared - * memory areas. + * this is also called at backend startup, to set up pointers to the + * already-initialized data structure. */ void InitShmemAllocator(PGShmemHeader *seghdr) { Size offset; + int64 hash_size; HASHCTL info; int hash_flags; size_t size; @@ -137,6 +621,16 @@ InitShmemAllocator(PGShmemHeader *seghdr) #endif Assert(seghdr != NULL); + if (IsUnderPostmaster) + { + Assert(shmem_startup_state == SB_INITIAL); + } + else + { + Assert(shmem_startup_state == SB_REQUESTING); + shmem_startup_state = SB_INITIALIZING; + } + /* * We assume the pointer and offset are MAXALIGN. Not a hard requirement, * but it's true today and keeps the math below simpler. @@ -181,9 +675,11 @@ InitShmemAllocator(PGShmemHeader *seghdr) * use ShmemInitHash() here because it relies on ShmemIndex being already * initialized. */ + hash_size = list_length(requested_shmem_areas) + SHMEM_INDEX_ADDITIONAL_SIZE; + info.keysize = SHMEM_INDEX_KEYSIZE; info.entrysize = sizeof(ShmemIndexEnt); - info.dsize = info.max_dsize = hash_select_dirsize(SHMEM_INDEX_SIZE); + info.dsize = info.max_dsize = hash_select_dirsize(hash_size); info.alloc = ShmemAllocNoError; hash_flags = HASH_ELEM | HASH_STRINGS | HASH_SHARED_MEM | HASH_ALLOC | HASH_DIRSIZE; if (!IsUnderPostmaster) @@ -194,10 +690,26 @@ InitShmemAllocator(PGShmemHeader *seghdr) else hash_flags |= HASH_ATTACH; info.hctl = ShmemAllocator->index; - ShmemIndex = hash_create("ShmemIndex", SHMEM_INDEX_SIZE, &info, hash_flags); + ShmemIndex = hash_create("ShmemIndex", hash_size, &info, hash_flags); Assert(ShmemIndex != NULL); } +/* + * Reset state on postmaster crash restart. + */ +void +ResetShmemAllocator(void) +{ + Assert(!IsUnderPostmaster); + shmem_startup_state = SB_INITIAL; + requested_shmem_areas = 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 * @@ -295,92 +807,128 @@ ShmemAddrIsValid(const void *addr) } /* - * 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. + * Register callbacks that define a shared memory area (or multiple areas). * - * Returns: pointer to the object. *foundPtr is set true if the object was - * already in the shmem index (hence, already initialized). + * The system will call the callbacks at different stages of postmaster or + * backend startup, to allocate and initialize 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. + * This is normally called early during postmaster startup, but if the + * SHMEM_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. */ -void * -ShmemInitStruct(const char *name, Size size, bool *foundPtr) +void +RegisterShmemCallbacks(const ShmemCallbacks *callbacks) { - ShmemIndexEnt *result; - void *structPtr; + if (shmem_startup_state == SB_DONE && IsUnderPostmaster) + { + /* After-startup initialization */ + ListCell *lc; + bool found = false; - Assert(ShmemIndex != NULL); + if ((callbacks->flags & SHMEM_ALLOW_AFTER_STARTUP) == 0) + elog(ERROR, "cannot request shared memory at this time"); - LWLockAcquire(ShmemIndexLock, LW_EXCLUSIVE); + Assert(requested_shmem_areas == NIL); + Assert(shmem_startup_state == SB_DONE); + shmem_startup_state = SB_REQUESTING; + if (callbacks->request_fn) + callbacks->request_fn(callbacks->request_fn_arg); + shmem_startup_state = SB_LATE_ATTACH_OR_INIT; - /* look it up in the shmem index */ - result = (ShmemIndexEnt *) - hash_search(ShmemIndex, name, HASH_ENTER_NULL, foundPtr); + LWLockAcquire(ShmemIndexLock, LW_EXCLUSIVE); - if (!result) - { - LWLockRelease(ShmemIndexLock); - ereport(ERROR, - (errcode(ERRCODE_OUT_OF_MEMORY), - errmsg("could not create ShmemIndex entry for data structure \"%s\"", - name))); - } + foreach(lc, requested_shmem_areas) + { + ShmemRequest *request = (ShmemRequest *) lfirst(lc); + + found = AttachOrInit(request, true, true); + } - 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). + * FIXME: What to do if multiple shmem areas were requested, and some + * of them are already initialized but not all? We expect all shmem + * areas requested by a single callback to form a coherent unit. */ - if (result->size != size) + if (found) { - LWLockRelease(ShmemIndexLock); - ereport(ERROR, - (errmsg("ShmemIndex entry size is wrong for data structure" - " \"%s\": expected %zu, actual %zu", - name, size, result->size))); + if (callbacks->attach_fn) + callbacks->attach_fn(callbacks->attach_fn_arg); } - 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) + else { - /* out of memory; remove the failed ShmemIndex entry */ - hash_search(ShmemIndex, name, HASH_REMOVE, NULL); - LWLockRelease(ShmemIndexLock); - ereport(ERROR, - (errcode(ERRCODE_OUT_OF_MEMORY), - errmsg("not enough shared memory for data structure" - " \"%s\" (%zu bytes requested)", - name, size))); + if (callbacks->init_fn) + callbacks->init_fn(callbacks->init_fn_arg); } - result->size = size; - result->allocated_size = allocated_size; - result->location = structPtr; + + LWLockRelease(ShmemIndexLock); + shmem_startup_state = SB_DONE; + return; } - LWLockRelease(ShmemIndexLock); + registered_shmem_callbacks = lappend(registered_shmem_callbacks, + (void *) callbacks); +} + +/* + * Call all shmem request callbacks. + */ +void +ShmemCallRequestCallbacks(void) +{ + ListCell *lc; - Assert(ShmemAddrIsValid(structPtr)); + Assert(shmem_startup_state == SB_INITIAL); + shmem_startup_state = SB_REQUESTING; - Assert(structPtr == (void *) CACHELINEALIGN(structPtr)); + foreach(lc, registered_shmem_callbacks) + { + const ShmemCallbacks *callbacks = (const ShmemCallbacks *) lfirst(lc); - return structPtr; + if (callbacks->request_fn) + callbacks->request_fn(callbacks->request_fn_arg); + } } +/* + * ShmemInitStruct -- Create/attach to a structure in shared memory. + * + * This is called during initialization to find or allocate + * a data structure in shared memory. If no other process + * has created the structure, this routine allocates space + * for it. If it exists already, a pointer to the existing + * structure is returned. + * + * Returns: pointer to the object. *foundPtr is set true if the object was + * already in the shmem index (hence, already initialized). + * + * Note: This is a legacy interface, kept for backwards compatibility with + * extensions. Use ShmemRequestStruct() in new code! + */ +void * +ShmemInitStruct(const char *name, Size size, bool *foundPtr) +{ + ShmemStructDesc desc; + ShmemRequestStructOpts options = { + .name = name, + .size = size, + }; + ShmemRequest request = {&desc, &options, SHMEM_KIND_STRUCT}; + + Assert(shmem_startup_state == SB_DONE || + shmem_startup_state == SB_INITIALIZING || + shmem_startup_state == SB_REQUESTING); + + /* look it up immediately */ + LWLockAcquire(ShmemIndexLock, LW_EXCLUSIVE); + *foundPtr = AttachOrInit(&request, true, true); + LWLockRelease(ShmemIndexLock); + + Assert(desc.ptr != NULL); + return desc.ptr; +} /* * Add two Size values, checking for overflow diff --git a/src/backend/storage/ipc/shmem_hash.c b/src/backend/storage/ipc/shmem_hash.c index b0c8d5939a0..48bb5d97c1a 100644 --- a/src/backend/storage/ipc/shmem_hash.c +++ b/src/backend/storage/ipc/shmem_hash.c @@ -1,11 +1,17 @@ /*------------------------------------------------------------------------- * * shmem_hash.c - * XXX + * hash table implementation in shared memory * * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * + * A shared memory hash table implementation on top of the named, fixed-size + * shared memory areas managed by shmem.c. Hash tables have a fixed maximum + * size, but their actual size can vary dynamically. When entries are added + * to the table, more space is allocated. Each shared data structure and hash + * has a string name to identify it, specified in its descriptor when its + * requested. * * IDENTIFICATION * src/backend/storage/ipc/shmem_hash.c @@ -16,6 +22,85 @@ #include "postgres.h" #include "storage/shmem.h" +#include "utils/memutils.h" + +/* + * ShmemRequestHash -- Request a shared memory hash table. + * + * Similar to ShmemRequestStruct(), but requests a hash table instead of an + * opaque area. + */ +void +ShmemRequestHash(ShmemHashDesc *desc, const ShmemRequestHashOpts *options) +{ + ShmemRequestHashOpts *options_copy; + int64 dirsize; + + Assert(options->name != NULL); + + options_copy = MemoryContextAlloc(TopMemoryContext, + sizeof(ShmemRequestHashOpts)); + memcpy(options_copy, options, sizeof(ShmemRequestHashOpts)); + + /* + * Hash tables allocated in shared memory have a fixed directory; it can't + * grow or other backends wouldn't be able to find it. So, make sure we + * make it big enough to start with. + * + * The shared memory allocator must be specified too. + */ + dirsize = hash_select_dirsize(options->max_size); + options_copy->hash_info.dsize = dirsize; + options_copy->hash_info.max_dsize = dirsize; + options_copy->hash_info.alloc = ShmemAllocNoError; + options_copy->hash_flags |= HASH_SHARED_MEM | HASH_ALLOC | HASH_DIRSIZE; + + /* + * Create a struct descriptor for the fixed-size area holding the hash + * table + */ + options_copy->base.name = options->name; + options_copy->base.size = hash_get_shared_size(&options_copy->hash_info, + options_copy->hash_flags); + + /* Reserve extra space for the buckets */ + options_copy->base.extra_size = + hash_estimate_size(options->max_size, options_copy->hash_info.entrysize) - options_copy->base.size; + + ShmemRequestInternal(&desc->base, &options_copy->base, SHMEM_KIND_HASH); +} + +void +shmem_hash_init(ShmemStructDesc *base_desc, const ShmemRequestStructOpts *base_options) +{ + ShmemHashDesc *desc = (ShmemHashDesc *) base_desc; + ShmemRequestHashOpts *options = (ShmemRequestHashOpts *) base_options; + int hash_flags = options->hash_flags; + + options->hash_info.hctl = desc->base.ptr; + Assert(options->hash_info.hctl != NULL); + desc->ptr = hash_create(desc->base.name, options->init_size, &options->hash_info, hash_flags); + + if (options->ptr) + *options->ptr = desc->ptr; +} + +void +shmem_hash_attach(ShmemStructDesc *base_desc, const ShmemRequestStructOpts *base_options) +{ + ShmemHashDesc *desc = (ShmemHashDesc *) base_desc; + ShmemRequestHashOpts *options = (ShmemRequestHashOpts *) base_options; + int hash_flags = options->hash_flags; + + /* attach to it rather than allocate and initialize new space */ + hash_flags |= HASH_ATTACH; + options->hash_info.hctl = desc->base.ptr; + Assert(options->hash_info.hctl != NULL); + desc->ptr = hash_create(desc->base.name, options->init_size, &options->hash_info, hash_flags); + + if (options->ptr) + *options->ptr = desc->ptr; +} /* @@ -41,9 +126,8 @@ * to shared-memory hash tables are added here, except that callers may * choose to specify HASH_PARTITION and/or HASH_FIXED_SIZE. * - * Note: before Postgres 9.0, this function returned NULL for some failure - * cases. Now, it always throws error instead, so callers need not check - * for NULL. + * Note: This is a legacy interface, kept for backwards compatibility with + * extensions. Use ShmemRequestHash() in new code! */ HTAB * ShmemInitHash(const char *name, /* table string name for shmem index */ diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index 5c47cf13473..9b880a6af65 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -121,6 +121,9 @@ FastPathLockShmemSize(void) size = add_size(size, mul_size(TotalProcs, (fpLockBitsSize + fpRelIdSize))); + Assert(TotalProcs > 0); + Assert(size > 0); + return size; } diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index b3563113219..278d2f20376 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -4164,7 +4164,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 11f5778eba1..b40e14abace 100644 --- a/src/backend/utils/hash/dynahash.c +++ b/src/backend/utils/hash/dynahash.c @@ -396,6 +396,8 @@ hash_create(const char *tabname, int64 nelem, const HASHCTL *info, int flags) } /* Initialize the hash header, plus a copy of the table name */ + Assert(tabname != NULL); + Assert(CurrentDynaHashCxt != NULL); hashp = (HTAB *) MemoryContextAlloc(CurrentDynaHashCxt, sizeof(HTAB) + strlen(tabname) + 1); MemSet(hashp, 0, sizeof(HTAB)); diff --git a/src/include/storage/shmem.h b/src/include/storage/shmem.h index 2a9e9becd26..a07fccfb1ba 100644 --- a/src/include/storage/shmem.h +++ b/src/include/storage/shmem.h @@ -24,17 +24,212 @@ #include "storage/spin.h" #include "utils/hsearch.h" +/* Different kinds of shmem areas. */ +typedef enum +{ + SHMEM_KIND_STRUCT = 0, /* plain, contiguous area of memory */ + SHMEM_KIND_HASH, /* a hash table */ +} ShmemAreaKind; + +/* + * ShmemStructDesc describes a named area or struct in shared memory. + * + * '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 ShmemStructDesc +{ + /* Name and size of the shared memory area. */ + const char *name; + + void *ptr; + size_t size; +} ShmemStructDesc; + +#define SHMEM_REQUEST_UNKNOWN_SIZE (-1) + +typedef struct ShmemRequestStructOpts +{ + const char *name; + + ssize_t size; + + /* + * Extra space to reserve in the shared memory segment, but it's not part + * of the struct itself. This is used for shared memory hash tables that + * can grow beyond the initial size when more buckets are allocated. + */ + size_t extra_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; +} ShmemRequestStructOpts; + +typedef struct ShmemHashDesc +{ + ShmemStructDesc base; + + /* + * 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; +} ShmemHashDesc; + +/* + * Descriptor for a named shared memory hash table. + * + * Similar to ShmemStructDesc, but describes a shared memory hash table. Each + * hash table is backed by an allocated area, described by 'base_desc', but if + * 'max_size' is greater than 'init_size', it can also grow beyond the initial + * allocated area by allocating more hash entries from the global unreserved + * space. + */ +typedef struct ShmemRequestHashOpts +{ + ShmemRequestStructOpts base; + + /* + * Name of the shared memory area. Required. Must be unique across the + * system. + */ + const char *name; + + /* + * max_size is the estimated maximum number of hashtable entries. This is + * not a hard limit, but the access efficiency will degrade if it is + * exceeded substantially (since it's used to compute directory size and + * the hash table buckets will get overfull). + */ + size_t max_size; + + /* + * init_size is the number of hashtable entries to preallocate. For a + * table whose maximum size is certain, this should be equal to max_size; + * that ensures that no run-time out-of-shared-memory failures can occur. + */ + size_t init_size; + + /* + * 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; +} ShmemRequestHashOpts; + + +typedef void (*ShmemRequestCallback) (void *arg); +typedef void (*ShmemInitCallback) (void *arg); +typedef void (*ShmemAttachCallback) (void *arg); + +/* + * Shared memory is reserved and allocated in stages at postmaster startup, + * and in EXEC_BACKEND mode, there's some extra work done to "attach" to them + * at backend startup. ShmemCallbacks holds callback functions that are + * called at different stages. + */ +typedef struct ShmemCallbacks +{ + /* SHMEM_* flags */ + int flags; + + /* + * 'request_fn' is called during postmaster startup, before the shared + * memory has been allocated. The function should call + * RequestShmemStruct() and RequestShmemHash() to register the subsystem's + * shared memory needs. + */ + ShmemRequestCallback request_fn; + void *request_fn_arg; + + /* + * Initialization callback function. This is called when the shared + * memory area is allocated, usually at postmaster startup. + */ + ShmemInitCallback init_fn; + void *init_fn_arg; + + /* + * Attachment callback function. In EXEC_BACKEND mode, this is called at + * startup of each backend. In !EXEC_BACKEND mode, this is only called if + * the shared memory area is registered after postmaster startup (see + * SHMEM_ALLOW_AFTER_STARTUP). + */ + ShmemAttachCallback attach_fn; + void *attach_fn_arg; +} ShmemCallbacks; + +/* + * Allow these shared memory allocations after postmaster startup. Normally, + * RegisterShmemCallbacks() errors out if it's called after postmaster startup + * e.g. in an add-in library loaded on-demaind in a backend. If you set this + * flag, RegisterShmemCallbacks() will instead immediately call the callbacks, + * to initialize or attach to the requested shared memory areas. + * + * This is not used by any built-in subsystems, but extensions can find it + * useful. + */ +#define SHMEM_ALLOW_AFTER_STARTUP 0x00000001 /* shmem.c */ typedef struct PGShmemHeader PGShmemHeader; /* avoid including * storage/pg_shmem.h here */ +extern void ResetShmemAllocator(void); extern void InitShmemAllocator(PGShmemHeader *seghdr); +#ifdef EXEC_BACKEND +extern void AttachShmemAllocator(PGShmemHeader *seghdr); +#endif extern void *ShmemAlloc(Size size); extern void *ShmemAllocNoError(Size size); extern bool ShmemAddrIsValid(const void *addr); + +extern void RegisterShmemCallbacks(const ShmemCallbacks *callbacks); + +extern void ShmemRequestInternal(ShmemStructDesc *desc, ShmemRequestStructOpts *options, + ShmemAreaKind kind); + +extern void ShmemRequestStruct(ShmemStructDesc *desc, const ShmemRequestStructOpts *options); +extern void ShmemRequestHash(ShmemHashDesc *desc, const ShmemRequestHashOpts *options); + +extern void ShmemCallRequestCallbacks(void); + +/* legacy shmem allocation functions */ extern HTAB *ShmemInitHash(const char *name, int64 init_size, int64 max_size, HASHCTL *infoP, int hash_flags); extern void *ShmemInitStruct(const char *name, Size size, bool *foundPtr); + +extern size_t ShmemGetRequestedSize(void); +extern void ShmemInitRequested(void); +#ifdef EXEC_BACKEND +extern void ShmemAttachRequested(void); +#endif + +extern void shmem_hash_init(ShmemStructDesc *base_desc, const ShmemRequestStructOpts *options); +extern void shmem_hash_attach(ShmemStructDesc *base_desc, const ShmemRequestStructOpts *options); + extern Size add_size(Size s1, Size s2); extern Size mul_size(Size s1, Size s2); @@ -43,19 +238,4 @@ extern PGDLLIMPORT Size pg_get_shmem_pagesize(void); /* ipci.c */ extern void RequestAddinShmemSpace(Size size); -/* size constants for the shmem index table */ - /* max size of data structure string name */ -#define SHMEM_INDEX_KEYSIZE (48) - /* estimated size of the shmem index table (not a hard limit) */ -#define SHMEM_INDEX_SIZE (64) - -/* this is a hash bucket in the shmem index table */ -typedef struct -{ - char key[SHMEM_INDEX_KEYSIZE]; /* string name */ - void *location; /* location in shared mem */ - Size size; /* # bytes requested for the structure */ - Size allocated_size; /* # bytes actually allocated */ -} ShmemIndexEnt; - #endif /* SHMEM_H */ diff --git a/src/test/modules/test_aio/test_aio.c b/src/test/modules/test_aio/test_aio.c index b1aa8af9ec0..d687408af0c 100644 --- a/src/test/modules/test_aio/test_aio.c +++ b/src/test/modules/test_aio/test_aio.c @@ -50,7 +50,6 @@ static InjIoErrorState *inj_io_error_state; static shmem_request_hook_type prev_shmem_request_hook = NULL; static shmem_startup_hook_type prev_shmem_startup_hook = NULL; - static PgAioHandle *last_handle; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 712d84128ca..d8d2548ef2d 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -2848,9 +2848,16 @@ SharedTypmodTableEntry Sharedsort ShellTypeInfo ShippableCacheEntry -ShmemAllocatorData ShippableCacheKey +ShmemAllocatorData +ShmemAreaKind +ShmemCallbacks ShmemIndexEnt +ShmemHashDesc +ShmemRequest +ShmemRequestHashOpts +ShmemRequestStructOpts +ShmemStructDesc ShutdownForeignScan_function ShutdownInformation ShutdownMode -- 2.47.3