diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml index 9ee1a2bfc6a..2ebec6928d5 100644 --- a/doc/src/sgml/system-views.sgml +++ b/doc/src/sgml/system-views.sgml @@ -4254,8 +4254,8 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx Anonymous allocations are allocations that have been made with ShmemAlloc() directly, rather than via - ShmemInitStruct() or - ShmemInitHash(). + ShmemRequestStruct() or + ShmemRequestHash(). diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml index 70e815b8a2c..789cac9fcab 100644 --- a/doc/src/sgml/xfunc.sgml +++ b/doc/src/sgml/xfunc.sgml @@ -3628,71 +3628,128 @@ CREATE FUNCTION make_array(anyelement) RETURNS anyarray Add-ins can reserve shared memory on server startup. To do so, the add-in's shared library must be preloaded by specifying it in shared_preload_libraries. - The shared library should also register a - shmem_request_hook in its - _PG_init function. This - shmem_request_hook can reserve shared memory by - calling: + The shared library should register callbacks in + its _PG_init function, which then get called at the + right stages of the system startup to initialize the shared memory. + Here is an example: -void RequestAddinShmemSpace(Size size) - - Each backend should obtain a pointer to the reserved shared memory by - calling: - -void *ShmemInitStruct(const char *name, Size size, bool *foundPtr) - - If this function sets foundPtr to - false, the caller should proceed to initialize the - contents of the reserved shared memory. If foundPtr - is set to true, the shared memory was already - initialized by another backend, and the caller need not initialize - further. - +typedef struct MyShmemData { + LWLock lock; /* protects the fields below */ - - To avoid race conditions, each backend should use the LWLock - AddinShmemInitLock when initializing its allocation - of shared memory, as shown here: - -static mystruct *ptr = NULL; -bool found; + ... shared memory contents ... +} MyShmemData; -LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE); -ptr = ShmemInitStruct("my struct name", size, &found); -if (!found) +static MyShmemData *MyShmem; /* pointer to the struct in shared memory */ + +static void my_shmem_request(void *arg); +static void my_shmem_init(void *arg); + +const ShmemCallbacks my_shmem_callbacks = { + .request_fn = my_shmem_request, + .init_fn = my_shmem_init, +}; + +/* + * Module load callback + */ +void +_PG_init(void) { - ... initialize contents of shared memory ... - ptr->locks = GetNamedLWLockTranche("my tranche name"); -} -LWLockRelease(AddinShmemInitLock); - - shmem_startup_hook provides a convenient place for the - initialization code, but it is not strictly required that all such code - be placed in this hook. On Windows (and anywhere else where - EXEC_BACKEND is defined), each backend executes the - registered shmem_startup_hook shortly after it - attaches to shared memory, so add-ins should still acquire - AddinShmemInitLock within this hook, as shown in the - example above. On other platforms, only the postmaster process executes - the shmem_startup_hook, and each backend automatically - inherits the pointers to shared memory. - + /* + * In order to create our shared memory area, we have to be loaded via + * shared_preload_libraries. + */ + if (!process_shared_preload_libraries_in_progress) + return; + /* Register our shared memory needs */ + RegisterShmemCallbacks(&my_shmem_callbacks); +} + +/* callback to request shmem space */ +static void +my_shmem_request(void *arg) +{ + ShmemRequestStruct(.name = "My shmem area", + .size = sizeof(MyShmemData), + .ptr = (void **) &MyShmem, + ); +} + +/* callback to initialize the contents of the MyShmem area at startup */ +static void +my_shmem_init(void *arg) +{ + int tranche_id; + + /* Initialize the lock */ + tranche_id = LWLockNewTrancheId("my tranche name"); + LWLockInitialize(&MyShmem->lock, tranche_id); + + ... initialize the rest of MyShmem fields ... +} + + + The request_fn callback is called during system + startup, before the shared memory has been allocated. It should call + ShmemRequestStruct() to register the add-in's + shared memory needs. Note that ShmemRequestStruct() + doesn't immediately allocate or initialize the memory, it merely + registers the space to be allocated later in the startup sequence. When + the memory is allocated, it is initialized to zero. For any more + complex initialization, set the init_fn() callback, + which will be called after the memory has been allocated and initialized + to zero, but before any other processes are running, and thus no locking + is required. + - An example of a shmem_request_hook and - shmem_startup_hook can be found in + On Windows, the attach_fn callback, if any, is + additionally called at every backend startup. It can be used to + initialize additional per-backend state related to the shared memory + area that is inherited via fork() on other systems. + + + An example of allocating shared memory can be found in contrib/pg_stat_statements/pg_stat_statements.c in the PostgreSQL source tree. - Requesting Shared Memory After Startup + Requesting Shared Memory After Startup with <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_CALLBACKS_ALLOW_AFTER_STARTUP flag in + the argument ShmemCallbacks struct to + acknowledge the risk. + + + When RegisterShmemCallbacks() is called after + startup, it will immediately call the appropriate callbacks, depending + on whether the requested memory areas were already initialized by + another backend. The callbacks will be called while holding an internal + lock, which prevents concurrent two backends from initializing the + memory area concurrently. + + + + + Allocating Dynamic Shared Memory After Startup There is another, more flexible method of reserving shared memory that - can be done after server startup and outside a - shmem_request_hook. To do so, each backend that will + can be done after server startup. To do so, each backend that will use the shared memory should obtain a pointer to it by calling: void *GetNamedDSMSegment(const char *name, size_t size, @@ -3711,10 +3768,7 @@ void *GetNamedDSMSegment(const char *name, size_t size, - Unlike shared memory reserved at server startup, there is no need to - acquire AddinShmemInitLock or otherwise take action - to avoid race conditions when reserving shared memory with - GetNamedDSMSegment. This function ensures that only + GetNamedDSMSegment ensures that only one backend allocates and initializes the segment and that all other backends receive a pointer to the fully allocated and initialized segment. diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c index ebd41176b94..3766d8231ac 100644 --- a/src/backend/bootstrap/bootstrap.c +++ b/src/backend/bootstrap/bootstrap.c @@ -39,6 +39,7 @@ #include "storage/fd.h" #include "storage/ipc.h" #include "storage/proc.h" +#include "storage/shmem_internal.h" #include "utils/builtins.h" #include "utils/fmgroids.h" #include "utils/guc.h" @@ -373,6 +374,7 @@ BootstrapModeMain(int argc, char *argv[], bool check_only) InitializeFastPathLocks(); + ShmemCallRequestCallbacks(); CreateSharedMemoryAndSemaphores(); /* diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c index 15b136ea29d..0973010b7dc 100644 --- a/src/backend/postmaster/launch_backend.c +++ b/src/backend/postmaster/launch_backend.c @@ -49,6 +49,7 @@ #include "replication/walreceiver.h" #include "storage/dsm.h" #include "storage/io_worker.h" +#include "storage/ipc.h" #include "storage/pg_shmem.h" #include "storage/shmem_internal.h" #include "tcop/backend_startup.h" @@ -673,7 +674,10 @@ SubPostmasterMain(int argc, char *argv[]) /* Restore basic shared memory pointers */ if (UsedShmemSegAddr != NULL) + { InitShmemAllocator(UsedShmemSegAddr); + ShmemCallRequestCallbacks(); + } /* * Run the appropriate Main function diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index eb4f3eb72d4..7a8ee19bdaf 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -115,6 +115,7 @@ #include "storage/ipc.h" #include "storage/pmsignal.h" #include "storage/proc.h" +#include "storage/shmem_internal.h" #include "tcop/backend_startup.h" #include "tcop/tcopprot.h" #include "utils/datetime.h" @@ -951,7 +952,14 @@ PostmasterMain(int argc, char *argv[]) InitializeFastPathLocks(); /* - * Give preloaded libraries a chance to request additional shared memory. + * Ask all subsystems, including preloaded libraries, to register their + * shared memory needs. + */ + ShmemCallRequestCallbacks(); + + /* + * Also call any legacy shmem request hooks that might've been installed + * by preloaded libraries. */ process_shmem_requests(); @@ -3232,7 +3240,14 @@ PostmasterStateMachine(void) /* re-read control file into local memory */ LocalProcessControlFile(true); - /* re-create shared memory and semaphores */ + /* + * Re-initialize shared memory and semaphores. Note: We don't call + * RegisterBuiltinShmemCallbacks(), we keep the old registrations. In + * order to re-register structs in extensions, we'd need to reload + * shared preload libraries, and we don't want to do that. + */ + ResetShmemAllocator(); + ShmemCallRequestCallbacks(); CreateSharedMemoryAndSemaphores(); UpdatePMState(PM_STARTUP); diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c index ca4e4727489..24422a80ab3 100644 --- a/src/backend/storage/ipc/ipci.c +++ b/src/backend/storage/ipc/ipci.c @@ -101,8 +101,9 @@ CalculateShmemSize(void) * during the actual allocation phase. */ size = 100000; - size = add_size(size, hash_estimate_size(SHMEM_INDEX_SIZE, - sizeof(ShmemIndexEnt))); + size = add_size(size, ShmemGetRequestedSize()); + + /* legacy subsystems */ size = add_size(size, dsm_estimate_size()); size = add_size(size, DSMRegistryShmemSize()); size = add_size(size, BufferManagerShmemSize()); @@ -177,6 +178,13 @@ AttachSharedMemoryStructs(void) */ InitializeFastPathLocks(); + /* + * Attach to LWLocks first. They are needed by most other subsystems. + */ + LWLockShmemInit(); + + /* Establish pointers to all shared memory areas in this backend */ + ShmemAttachRequested(); CreateOrAttachShmemStructs(); /* @@ -221,7 +229,17 @@ CreateSharedMemoryAndSemaphores(void) */ InitShmemAllocator(seghdr); - /* Initialize subsystems */ + /* + * Initialize LWLocks first, in case any of the shmem init function use + * LWLocks. (Nothing else can be running during startup, so they don't + * need to do any locking yet, but we nevertheless allow it.) + */ + LWLockShmemInit(); + + /* Initialize all shmem areas */ + ShmemInitRequested(); + + /* Initialize legacy subsystems */ CreateOrAttachShmemStructs(); /* Initialize dynamic shared memory facilities. */ @@ -252,11 +270,6 @@ CreateSharedMemoryAndSemaphores(void) static void CreateOrAttachShmemStructs(void) { - /* - * Set up LWLocks. They are needed by most other subsystems. - */ - LWLockShmemInit(); - dsm_shmem_init(); DSMRegistryShmemInit(); diff --git a/src/backend/storage/ipc/shmem.c b/src/backend/storage/ipc/shmem.c index e91c47d4d97..e1724b0358d 100644 --- a/src/backend/storage/ipc/shmem.c +++ b/src/backend/storage/ipc/shmem.c @@ -19,43 +19,109 @@ * methods). The routines in this file are used for allocating and * binding to shared memory data structures. * - * NOTES: - * (a) There are three kinds of shared memory data structures - * available to POSTGRES: fixed-size structures, queues and hash - * tables. Fixed-size structures contain things like global variables - * for a module and should never be allocated after the shared memory - * initialization phase. Hash tables have a fixed maximum size and - * cannot grow beyond that. Queues link data structures - * that have been allocated either within fixed-size structures or as hash - * buckets. Each shared data structure has a string name to identify - * it (assigned in the module that declares it). + * This module provides facilities to allocate fixed-size structures in shared + * memory, for things like variables shared between all backend processes. + * Each such structure has a string name to identify it, specified when it is + * requested. shmem_hash.c provides a shared hash table implementation on top + * of that. * - * (b) During initialization, each module looks for its - * shared data structures in a hash table called the "Shmem Index". - * If the data structure is not present, the caller can allocate - * a new one and initialize it. If the data structure is present, - * the caller "attaches" to the structure by initializing a pointer - * in the local address space. - * The shmem index has two purposes: first, it gives us - * a simple model of how the world looks when a backend process - * initializes. If something is present in the shmem index, - * it is initialized. If it is not, it is uninitialized. Second, - * the shmem index allows us to allocate shared memory on demand - * instead of trying to preallocate structures and hard-wire the - * sizes and locations in header files. If you are using a lot - * of shared memory in a lot of different places (and changing - * things during development), this is important. + * Shared memory areas should usually not be allocated after postmaster + * startup, although we do allow small allocations later for the benefit of + * extension modules that are loaded after startup. Despite that allowance, + * extensions that need shared memory should be added in + * shared_preload_libraries, because the allowance is quite small and there is + * no guarantee that any memory is available after startup. * - * (c) In standard Unix-ish environments, individual backends do not - * need to re-establish their local pointers into shared memory, because - * they inherit correct values of those variables via fork() from the - * postmaster. However, this does not work in the EXEC_BACKEND case. - * In ports using EXEC_BACKEND, new backends have to set up their local - * pointers using the method described in (b) above. + * Nowadays, there is also another way to allocate shared memory called + * Dynamic Shared Memory. See dsm.c for that facility. One big difference + * between traditional shared memory handled by shmem.c and dynamic shared + * memory is that traditional shared memory areas are mapped to the same + * address in all processes, so you can use normal pointers in shared memory + * structs. With Dynamic Shared Memory, you must use offsets or DSA pointers + * instead. * - * (d) memory allocation model: shared memory can never be - * freed, once allocated. Each hash table has its own free list, - * so hash buckets can be reused when an item is deleted. + * Shared memory managed by shmem.c can never be freed, once allocated. Each + * hash table has its own free list, so hash buckets can be reused when an + * item is deleted. + * + * Usage + * ----- + * + * To allocate shared memory, you need to register a set of callback functions + * which handle the lifecycle of the allocation. In the request_fn callback, + * call ShmemRequestStruct() with the desired name and size. When the area is + * later allocated or attached to, the global variable pointed to by the .ptr + * option is set to the shared memory location of the allocation. The init_fn + * callback can perform additional initialization. + * + * typedef struct MyShmemData { + * ... + * } MyShmemData; + * + * static MyShmemData *MyShmem; + * + * static void my_shmem_request(void *arg); + * static void my_shmem_init(void *arg); + * + * const ShmemCallbacks MyShmemCallbacks = { + * .request_fn = my_shmem_request, + * .init_fn = my_shmem_init, + * }; + * + * static void + * my_shmem_request(void *arg) + * { + * ShmemRequestStruct(.name = "My shmem area", + * .size = sizeof(MyShmemData), + * .ptr = (void **) &MyShmem, + * ); + * } + * + * Register the callbacks by calling RegisterShmemCallbacks(&MyShmemCallbacks) + * in the extension's _PG_init() function. + * + * Lifecycle + * --------- + * + * Initializing shared memory happens in multiple phases. In the first phase, + * during postmaster startup, all the request_fn callbacks are called. Only + * after all the request_fn callbacks have been called and all the shmem areas + * have been requested by the ShmemRequestStruct() calls we know how much + * shared memory we need in total. After that, postmaster allocates global + * shared memory segment, and calls all the init_fn callbacks to initialize + * all the requested shmem areas. + * + * In standard Unix-ish environments, individual backends do not need to + * re-establish their local pointers into shared memory, because they inherit + * correct values of those variables via fork() from the postmaster. However, + * this does not work in the EXEC_BACKEND case. In ports using EXEC_BACKEND, + * backend startup also calls the shmem_request callbacks to re-establish the + * knowledge about each shared memory area, sets the pointer variables + * (*options->ptr), and calls the attach_fn callback, if any, for additional + * per-backend setup. + * + * Legacy ShmemInitStruct()/ShmemInitHash() functions + * -------------------------------------------------- + * + * ShmemInitStruct()/ShmemInitHash() is another way of registering shmem + * areas. It pre-dates the ShmemRequestStruct()/ShmemRequestHash() functions, + * and should not be used in new code, but as of this writing it is still + * widely used in extensions. + * + * To allocate a shmem area with ShmemInitStruct(), you need to separately + * register the size needed for the area by calling RequestAddinShmemSpace() + * from the extension's shmem_request_hook, and allocate the area by calling + * ShmemInitStruct() from the extension's shmem_startup_hook. There are no + * init/attach callbacks. Instead, the caller of ShmemInitStruct() must check + * the return status of ShmemInitStruct() and initialize the struct if it was + * not previously initialized. + * + * Calling ShmemAlloc() directly + * ----------------------------- + * + * There's a more low-level way of allocating shared memory too: you can call + * ShmemAlloc() directly. It's used to implement the higher level mechanisms, + * and should generally not be called directly. */ #include "postgres.h" @@ -75,6 +141,75 @@ #include "utils/builtins.h" #include "utils/tuplestore.h" +/* + * Registered callbacks. + * + * During postmaster startup, we accumulate the callbacks from all subsystems + * in this list. + * + * This is in process private memory, although on Unix-like systems, we expect + * all the registrations to happen at postmaster startup time and be inherited + * by all the child processes via fork(). + */ +static List *registered_shmem_callbacks; + +/* + * In the shmem request phase, all the shmem areas requested with the + * ShmemRequest*() functions are accumulated here. + */ +typedef struct +{ + ShmemStructOpts *options; + ShmemRequestKind kind; +} ShmemRequest; + +static List *pending_shmem_requests; + +/* + * Per-process state machine, for sanity checking that we do things in the + * right order. + * + * Postmaster: + * INITIAL -> REQUESTING -> INITIALIZING -> DONE + * + * Backends in EXEC_BACKEND mode: + * INITIAL -> REQUESTING -> ATTACHING -> DONE + * + * Late request: + * DONE -> REQUESTING -> AFTER_STARTUP_ATTACH_OR_INIT -> DONE + */ +enum shmem_request_state +{ + /* Initial state */ + SRS_INITIAL, + + /* + * When we start calling the shmem_request callbacks, we enter the + * SRS_REQUESTING phase. All ShmemRequestStruct calls happen in this + * state. + */ + SRS_REQUESTING, + + /* + * Postmaster has finished all shmem requests, and is now initializing the + * shared memory segment. init_fn callbacks are called in this state. + */ + SRS_INITIALIZING, + + /* + * A postmaster child process is starting up. attach_fn callbacks are + * called in this state. + */ + SRS_ATTACHING, + + /* An after-startup allocation or attachment is in progress */ + SRS_AFTER_STARTUP_ATTACH_OR_INIT, + + /* Normal state after shmem initialization / attachment */ + SRS_DONE, +}; +static enum shmem_request_state shmem_request_state = SRS_INITIAL; + /* * This is the first data structure stored in the shared memory segment, at * the offset that PGShmemHeader->content_offset points to. Allocations by @@ -106,25 +241,380 @@ static void *ShmemBase; /* start address of shared memory */ static void *ShmemEnd; /* end+1 address of shared memory */ static ShmemAllocatorData *ShmemAllocator; -static HTAB *ShmemIndex = NULL; /* primary index hashtable for shmem */ + +/* + * ShmemIndex is a global directory of shmem areas, itself also stored in the + * shared memory. + */ +static HTAB *ShmemIndex; + + /* max size of data structure string name */ +#define SHMEM_INDEX_KEYSIZE (48) + +/* + * # of additional entries to reserve in the shmem index table, for + * allocations after postmaster startup. (This is not a hard limit, the hash + * table can grow larger than that if there is shared memory available) + */ +#define SHMEM_INDEX_ADDITIONAL_SIZE (128) + +/* this is a hash bucket in the shmem index table */ +typedef struct +{ + char key[SHMEM_INDEX_KEYSIZE]; /* string name */ + void *location; /* location in shared mem */ + Size size; /* # bytes requested for the structure */ + Size allocated_size; /* # bytes actually allocated */ +} ShmemIndexEnt; /* To get reliable results for NUMA inquiry we need to "touch pages" once */ static bool firstNumaTouch = true; +static void CallShmemCallbacksAfterStartup(const ShmemCallbacks *callbacks); +static void InitShmemIndexEntry(ShmemRequest *request); +static bool AttachShmemIndexEntry(ShmemRequest *request, bool missing_ok); + Datum pg_numa_available(PG_FUNCTION_ARGS); +/* + * ShmemRequestStruct() --- request a named shared memory area + * + * Subsystems call this to register their shared memory needs. This is + * usually done early in postmaster startup, before the shared memory segment + * has been created, so that the size can be included in the estimate for + * total amount of shared memory needed. We set aside a small amount of + * memory for allocations that happen later, for the benefit of non-preloaded + * extensions, but that should not be relied upon. + * + * This does not yet allocate the memory, but merely registers the need for + * it. The actual allocation happens later in the postmaster startup + * sequence. + * + * This must be called from a shmem_request callback function, registered with + * RegisterShmemCallbacks(). This enforces a coding pattern that works the + * same in normal Unix systems and with EXEC_BACKEND. On Unix systems, the + * shmem_request callbacks are called once, early in postmaster startup, and + * the child processes inherit the struct descriptors and any other + * per-process state from the postmaster. In EXEC_BACKEND mode, shmem_request + * callbacks are *also* called in each backend, at backend startup, to + * re-establish the struct descriptors. By calling the same function in both + * cases, we ensure that all the shmem areas are registered the same way in + * all processes. + * + * 'desc' is a backend-private handle for the shared memory area. + * + * 'options' defines the name and size of the area, and any other optional + * features. Leave unused options as zeros. The options are copied to + * longer-lived memory, so it doesn't need to live after the + * ShmemRequestStruct() call and can point to a local variable in the calling + * function. The 'name' must point to a long-lived string though, only the + * pointer to it is copied. + */ +void +ShmemRequestStructWithOpts(const ShmemStructOpts *options) +{ + ShmemStructOpts *options_copy; + + options_copy = MemoryContextAlloc(TopMemoryContext, + sizeof(ShmemStructOpts)); + memcpy(options_copy, options, sizeof(ShmemStructOpts)); + + ShmemRequestInternal(options_copy, SHMEM_KIND_STRUCT); +} + +/* + * Internal workhorse of ShmemRequestStruct() and ShmemRequestHash(). + * + * Note: 'options' must live until the init/attach callbacks have been called. + * Unlike in the public ShmemRequestStruct() and ShmemRequestHash() functions, + * 'options' is *not* copied. This allows ShmemRequestHash() to pass a + * pointer to the extended ShmemHashOpts struct instead. + */ +void +ShmemRequestInternal(ShmemStructOpts *options, ShmemRequestKind kind) +{ + ShmemRequest *request; + + if (options->name == NULL) + elog(ERROR, "shared memory request is missing 'name' option"); + + if (IsUnderPostmaster) + { + if (options->size <= 0 && options->size != SHMEM_ATTACH_UNKNOWN_SIZE) + elog(ERROR, "invalid size %zd for shared memory request for \"%s\"", + options->size, options->name); + } + else + { + if (options->size == SHMEM_ATTACH_UNKNOWN_SIZE) + elog(ERROR, "SHMEM_ATTACH_UNKNOWN_SIZE cannot be used during startup"); + if (options->size <= 0) + elog(ERROR, "invalid size %zd for shared memory request for \"%s\"", + options->size, options->name); + } + + if (shmem_request_state != SRS_REQUESTING) + elog(ERROR, "ShmemRequestStruct can only be called from a shmem_request callback"); + + /* Check that it's not already registered in this process */ + foreach_ptr(ShmemRequest, existing, pending_shmem_requests) + { + if (strcmp(existing->options->name, options->name) == 0) + ereport(ERROR, + (errmsg("shared memory struct \"%s\" is already registered", + options->name))); + } + + /* Request looks valid, remember it */ + request = palloc(sizeof(ShmemRequest)); + request->options = options; + request->kind = kind; + pending_shmem_requests = lappend(pending_shmem_requests, request); +} + +/* + * ShmemGetRequestedSize() --- estimate the total size of all registered shared + * memory structures. + * + * This is called at postmaster startup, before the shared memory segment has + * been created. + */ +size_t +ShmemGetRequestedSize(void) +{ + size_t size; + + /* memory needed for the ShmemIndex */ + size = hash_estimate_size(list_length(pending_shmem_requests) + SHMEM_INDEX_ADDITIONAL_SIZE, + sizeof(ShmemIndexEnt)); + size = CACHELINEALIGN(size); + + /* memory needed for all the requested areas */ + foreach_ptr(ShmemRequest, request, pending_shmem_requests) + { + size = add_size(size, request->options->size); + /* calculate alignment padding like ShmemAllocRaw() does */ + size = CACHELINEALIGN(size); + } + + return size; +} + +/* + * ShmemInitRequested() --- allocate and initialize requested shared memory + * structures. + * + * This is called once at postmaster startup, after the shared memory segment + * has been created. + */ +void +ShmemInitRequested(void) +{ + /* should be called only by the postmaster or a standalone backend */ + Assert(!IsUnderPostmaster); + Assert(shmem_request_state == SRS_INITIALIZING); + + /* + * Initialize the ShmemIndex entries and perform basic initialization of + * all the requested memory areas. There are no concurrent processes yet, + * so no need for locking. + */ + foreach_ptr(ShmemRequest, request, pending_shmem_requests) + { + InitShmemIndexEntry(request); + } + list_free_deep(pending_shmem_requests); + pending_shmem_requests = NIL; + + /* + * Call the subsystem-specific init callbacks to finish initialization of + * all the areas. + */ + foreach_ptr(const ShmemCallbacks, callbacks, registered_shmem_callbacks) + { + if (callbacks->init_fn) + callbacks->init_fn(callbacks->opaque_arg); + } + + shmem_request_state = SRS_DONE; +} + +/* + * Re-establish process private state related to shmem areas. + * + * This is called at backend startup in EXEC_BACKEND mode, in every backend. + */ +#ifdef EXEC_BACKEND +void +ShmemAttachRequested(void) +{ + ListCell *lc; + + /* Must be initializing a (non-standalone) backend */ + Assert(IsUnderPostmaster); + Assert(ShmemAllocator->index != NULL); + Assert(shmem_request_state == SRS_REQUESTING); + shmem_request_state = SRS_ATTACHING; + + LWLockAcquire(ShmemIndexLock, LW_SHARED); + + /* + * Attach to all the requested memory areas. + */ + foreach_ptr(ShmemRequest, request, pending_shmem_requests) + { + AttachShmemIndexEntry(request, false); + } + list_free_deep(pending_shmem_requests); + pending_shmem_requests = NIL; + + /* Call attach callbacks */ + foreach(lc, registered_shmem_callbacks) + { + const ShmemCallbacks *callbacks = (const ShmemCallbacks *) lfirst(lc); + + if (callbacks->attach_fn) + callbacks->attach_fn(callbacks->opaque_arg); + } + + LWLockRelease(ShmemIndexLock); + + shmem_request_state = SRS_DONE; +} +#endif + +/* + * Insert requested shmem area into the shared memory index and initialize it. + * + * Note that this only does performs basic initialization depending on + * ShmemRequestKind, like setting the global pointer variable to the area for + * SHMEM_KIND_STRUCT or setting up the backend-private HTAB control struct. + * This does *not* call the subsystem-specific init callbacks. That's done + * later after all the shmem areas have been initialized or attached to. + */ +static void +InitShmemIndexEntry(ShmemRequest *request) +{ + const char *name = request->options->name; + ShmemIndexEnt *index_entry; + bool found; + size_t allocated_size; + void *structPtr; + + /* look it up in the shmem index */ + index_entry = (ShmemIndexEnt *) + hash_search(ShmemIndex, name, HASH_ENTER_NULL, &found); + if (found) + elog(ERROR, "shared memory struct \"%s\" is already initialized", name); + if (!index_entry) + { + /* tried to add it to the hash table, but there was no space */ + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("could not create ShmemIndex entry for data structure \"%s\"", + name))); + } + + /* + * We inserted the entry to the shared memory index. Allocate requested + * amount of shared memory for it, and initialize the index entry. + */ + structPtr = ShmemAllocRaw(request->options->size, &allocated_size); + if (structPtr == NULL) + { + /* out of memory; remove the failed ShmemIndex entry */ + hash_search(ShmemIndex, name, HASH_REMOVE, NULL); + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("not enough shared memory for data structure" + " \"%s\" (%zu bytes requested)", + name, request->options->size))); + } + index_entry->size = request->options->size; + index_entry->allocated_size = allocated_size; + index_entry->location = structPtr; + + /* Initialize depending on the kind of shmem area it is */ + switch (request->kind) + { + case SHMEM_KIND_STRUCT: + if (request->options->ptr) + *(request->options->ptr) = index_entry->location; + break; + case SHMEM_KIND_HASH: + shmem_hash_init(structPtr, request->options); + break; + } +} + +/* + * Look up a named shmem area in the shared memory index and attach to it. + * + * Note that this only performs the basic attachment actions depending on + * ShmemRequestKind, like setting the global pointer variable to the area for + * SHMEM_KIND_STRUCT or setting up the backend-private HTAB control struct. + * This does *not* call the subsystem-specific attach callbacks. That's done + * later after all the shmem areas have been initialized or attached to. + */ +static bool +AttachShmemIndexEntry(ShmemRequest *request, bool missing_ok) +{ + const char *name = request->options->name; + ShmemIndexEnt *index_entry; + + /* Look it up in the shmem index */ + index_entry = (ShmemIndexEnt *) + hash_search(ShmemIndex, name, HASH_FIND, NULL); + if (!index_entry) + { + if (!missing_ok) + ereport(ERROR, + (errmsg("could not find ShmemIndex entry for data structure \"%s\"", + request->options->name))); + return false; + } + + /* Check that the size in the index matches the request */ + if (index_entry->size != request->options->size && + request->options->size != SHMEM_ATTACH_UNKNOWN_SIZE) + { + ereport(ERROR, + (errmsg("shared memory struct \"%s\" was created with" + " different size: existing %zu, requested %zu", + name, index_entry->size, request->options->size))); + } + + /* + * Re-establish the caller's pointer variable, or do other actions to + * attach depending on the kind of shmem area it is. + */ + switch (request->kind) + { + case SHMEM_KIND_STRUCT: + if (request->options->ptr) + *(request->options->ptr) = index_entry->location; + break; + case SHMEM_KIND_HASH: + shmem_hash_attach(index_entry->location, request->options); + break; + } + + return true; +} + /* * InitShmemAllocator() --- set up basic pointers to shared memory. * * Called at postmaster or stand-alone backend startup, to initialize the * allocator's data structure in the shared memory segment. In EXEC_BACKEND, - * this is also called at backend startup, to set up pointers to the shared - * memory areas. + * this is also called at backend startup, to set up pointers to the + * already-initialized data structure. */ void InitShmemAllocator(PGShmemHeader *seghdr) { Size offset; + int64 hash_nelems; HASHCTL info; int hash_flags; @@ -133,6 +623,16 @@ InitShmemAllocator(PGShmemHeader *seghdr) #endif Assert(seghdr != NULL); + if (IsUnderPostmaster) + { + Assert(shmem_request_state == SRS_INITIAL); + } + else + { + Assert(shmem_request_state == SRS_REQUESTING); + shmem_request_state = SRS_INITIALIZING; + } + /* * We assume the pointer and offset are MAXALIGN. Not a hard requirement, * but it's true today and keeps the math below simpler. @@ -177,19 +677,21 @@ InitShmemAllocator(PGShmemHeader *seghdr) * use ShmemInitHash() here because it relies on ShmemIndex being already * initialized. */ + hash_nelems = list_length(pending_shmem_requests) + SHMEM_INDEX_ADDITIONAL_SIZE; + info.keysize = SHMEM_INDEX_KEYSIZE; info.entrysize = sizeof(ShmemIndexEnt); hash_flags = HASH_ELEM | HASH_STRINGS | HASH_FIXED_SIZE; if (!IsUnderPostmaster) { - ShmemAllocator->index_size = hash_estimate_size(SHMEM_INDEX_SIZE, info.entrysize); + ShmemAllocator->index_size = hash_estimate_size(hash_nelems, info.entrysize); ShmemAllocator->index = (HASHHDR *) ShmemAlloc(ShmemAllocator->index_size); } ShmemIndex = shmem_hash_create(ShmemAllocator->index, ShmemAllocator->index_size, IsUnderPostmaster, - "ShmemIndex", SHMEM_INDEX_SIZE, + "ShmemIndex", hash_nelems, &info, hash_flags); Assert(ShmemIndex != NULL); @@ -210,6 +712,23 @@ InitShmemAllocator(PGShmemHeader *seghdr) } } +/* + * Reset state on postmaster crash restart. + */ +void +ResetShmemAllocator(void) +{ + Assert(!IsUnderPostmaster); + shmem_request_state = SRS_INITIAL; + + pending_shmem_requests = NIL; + + /* + * Note that we don't clear the registered callbacks. We will need to + * call them again as we restart + */ +} + /* * ShmemAlloc -- allocate max-aligned chunk from shared memory * @@ -306,6 +825,144 @@ ShmemAddrIsValid(const void *addr) return (addr >= ShmemBase) && (addr < ShmemEnd); } +/* + * Register callbacks that define a shared memory area (or multiple areas). + * + * The system will call the callbacks at different stages of postmaster or + * backend startup, to allocate and initialize the area. + * + * This is normally called early during postmaster startup, but if the + * SHMEM_CALLBACKS_ALLOW_AFTER_STARTUP is set, this can also be used after + * startup, although after startup there's no guarantee that there's enough + * shared memory available. When called after startup, this immediately calls + * the right callbacks depending on whether another backend had already + * initialized the area. + * + * Note: In EXEC_BACKEND mode, this needs to be called in every backend + * process. That's needed because we cannot pass down the callback function + * pointers from the postmaster process, because different processes may have + * loaded libraries to different addresses. + */ +void +RegisterShmemCallbacks(const ShmemCallbacks *callbacks) +{ + if (shmem_request_state == SRS_DONE && IsUnderPostmaster) + { + /* + * After-startup initialization or attachment. Call the appropriate + * callbacks immediately. + */ + if ((callbacks->flags & SHMEM_CALLBACKS_ALLOW_AFTER_STARTUP) == 0) + elog(ERROR, "cannot request shared memory at this time"); + + CallShmemCallbacksAfterStartup(callbacks); + } + else + { + /* Remember the callbacks for later */ + registered_shmem_callbacks = lappend(registered_shmem_callbacks, + (void *) callbacks); + } +} + +/* + * Register a shmem area (or multiple areas) after startup. + */ +static void +CallShmemCallbacksAfterStartup(const ShmemCallbacks *callbacks) +{ + bool found_any; + bool notfound_any; + + Assert(shmem_request_state == SRS_DONE); + shmem_request_state = SRS_REQUESTING; + + /* + * Call the request callback first. The callback makes ShmemRequest*() + * calls for each shmem area, adding them to pending_shmem_requests. + */ + Assert(pending_shmem_requests == NIL); + if (callbacks->request_fn) + callbacks->request_fn(callbacks->opaque_arg); + shmem_request_state = SRS_AFTER_STARTUP_ATTACH_OR_INIT; + + if (pending_shmem_requests == NIL) + { + shmem_request_state = SRS_DONE; + return; + } + + /* Hold ShmemIndexLock while we allocate all the shmem entries */ + LWLockAcquire(ShmemIndexLock, LW_EXCLUSIVE); + + /* + * Check if the requested shared memory areas have already been + * initialized. We assume all the areas requested by the request callback + * to form a coherent unit such that they're all already initialized or + * none. Otherwise it would be ambiguous which callback, init or attach, + * to callback afterwards. + */ + found_any = notfound_any = false; + foreach_ptr(ShmemRequest, request, pending_shmem_requests) + { + if (hash_search(ShmemIndex, request->options->name, HASH_FIND, NULL)) + found_any = true; + else + notfound_any = true; + } + if (found_any && notfound_any) + elog(ERROR, "found some but not all"); + + /* + * Allocate or attach all the shmem areas requested by the request_fn + * callback. + */ + foreach_ptr(ShmemRequest, request, pending_shmem_requests) + { + if (found_any) + AttachShmemIndexEntry(request, false); + else + InitShmemIndexEntry(request); + } + list_free_deep(pending_shmem_requests); + pending_shmem_requests = NIL; + + /* Finish by calling the appropriate subsystem-specific callback */ + if (found_any) + { + if (callbacks->attach_fn) + callbacks->attach_fn(callbacks->opaque_arg); + } + else + { + if (callbacks->init_fn) + callbacks->init_fn(callbacks->opaque_arg); + } + + LWLockRelease(ShmemIndexLock); + shmem_request_state = SRS_DONE; +} + +/* + * Call all shmem request callbacks. + */ +void +ShmemCallRequestCallbacks(void) +{ + ListCell *lc; + + Assert(shmem_request_state == SRS_INITIAL); + shmem_request_state = SRS_REQUESTING; + + foreach(lc, registered_shmem_callbacks) + { + const ShmemCallbacks *callbacks = (const ShmemCallbacks *) lfirst(lc); + + if (callbacks->request_fn) + callbacks->request_fn(callbacks->opaque_arg); + } +} + /* * ShmemInitStruct -- Create/attach to a structure in shared memory. * @@ -318,82 +975,43 @@ ShmemAddrIsValid(const void *addr) * Returns: pointer to the object. *foundPtr is set true if the object was * already in the shmem index (hence, already initialized). * - * Note: before Postgres 9.0, this function returned NULL for some failure - * cases. Now, it always throws error instead, so callers need not check - * for NULL. + * Note: This is a legacy interface, kept for backwards compatibility with + * extensions. Use ShmemRequestStruct() in new code! */ void * ShmemInitStruct(const char *name, Size size, bool *foundPtr) { - ShmemIndexEnt *result; - void *structPtr; + void *ptr = NULL; + ShmemStructOpts options = { + .name = name, + .size = size, + .ptr = &ptr, + }; + ShmemRequest request = {&options, SHMEM_KIND_STRUCT}; - Assert(ShmemIndex != NULL); + Assert(shmem_request_state == SRS_DONE || + shmem_request_state == SRS_INITIALIZING || + shmem_request_state == SRS_REQUESTING); LWLockAcquire(ShmemIndexLock, LW_EXCLUSIVE); - /* look it up in the shmem index */ - result = (ShmemIndexEnt *) - hash_search(ShmemIndex, name, HASH_ENTER_NULL, foundPtr); + /* + * During postmaster startup, look up the existing entry if any. + */ + *foundPtr = false; + if (IsUnderPostmaster) + *foundPtr = AttachShmemIndexEntry(&request, true); - if (!result) - { - LWLockRelease(ShmemIndexLock); - ereport(ERROR, - (errcode(ERRCODE_OUT_OF_MEMORY), - errmsg("could not create ShmemIndex entry for data structure \"%s\"", - name))); - } - - if (*foundPtr) - { - /* - * Structure is in the shmem index so someone else has allocated it - * already. The size better be the same as the size we are trying to - * initialize to, or there is a name conflict (or worse). - */ - if (result->size != size) - { - LWLockRelease(ShmemIndexLock); - ereport(ERROR, - (errmsg("ShmemIndex entry size is wrong for data structure" - " \"%s\": expected %zu, actual %zu", - name, size, result->size))); - } - structPtr = result->location; - } - else - { - Size allocated_size; - - /* It isn't in the table yet. allocate and initialize it */ - structPtr = ShmemAllocRaw(size, &allocated_size); - if (structPtr == NULL) - { - /* out of memory; remove the failed ShmemIndex entry */ - hash_search(ShmemIndex, name, HASH_REMOVE, NULL); - LWLockRelease(ShmemIndexLock); - ereport(ERROR, - (errcode(ERRCODE_OUT_OF_MEMORY), - errmsg("not enough shared memory for data structure" - " \"%s\" (%zu bytes requested)", - name, size))); - } - result->size = size; - result->allocated_size = allocated_size; - result->location = structPtr; - } + /* Initialize it if not found */ + if (!*foundPtr) + InitShmemIndexEntry(&request); LWLockRelease(ShmemIndexLock); - Assert(ShmemAddrIsValid(structPtr)); - - Assert(structPtr == (void *) CACHELINEALIGN(structPtr)); - - return structPtr; + Assert(ptr != NULL); + return ptr; } - /* * Add two Size values, checking for overflow */ diff --git a/src/backend/storage/ipc/shmem_hash.c b/src/backend/storage/ipc/shmem_hash.c index 1ca4a321c0f..c28d673cbd2 100644 --- a/src/backend/storage/ipc/shmem_hash.c +++ b/src/backend/storage/ipc/shmem_hash.c @@ -20,6 +20,7 @@ #include "storage/shmem.h" #include "storage/shmem_internal.h" +#include "utils/memutils.h" /* * A very simple allocator used to carve out different parts of a hash table @@ -33,6 +34,66 @@ typedef struct shmem_hash_allocator static void *ShmemHashAlloc(Size size, void *alloc_arg); +/* + * ShmemRequestHash -- Request a shared memory hash table. + * + * Similar to ShmemRequestStruct(), but requests a hash table instead of an + * opaque area. + */ +void +ShmemRequestHashWithOpts(const ShmemHashOpts *options) +{ + ShmemHashOpts *options_copy; + + Assert(options->name != NULL); + + options_copy = MemoryContextAlloc(TopMemoryContext, + sizeof(ShmemHashOpts)); + memcpy(options_copy, options, sizeof(ShmemHashOpts)); + + /* Set options for the fixed-size area holding the hash table */ + options_copy->base.name = options->name; + options_copy->base.size = hash_estimate_size(options_copy->nelems, + options_copy->hash_info.entrysize); + + ShmemRequestInternal(&options_copy->base, SHMEM_KIND_HASH); +} + +void +shmem_hash_init(void *location, ShmemStructOpts *base_options) +{ + ShmemHashOpts *options = (ShmemHashOpts *) base_options; + int hash_flags = options->hash_flags; + HTAB *htab; + + options->hash_info.hctl = location; + htab = shmem_hash_create(location, options->base.size, false, + options->name, + options->nelems, &options->hash_info, hash_flags); + + if (options->ptr) + *options->ptr = htab; +} + +void +shmem_hash_attach(void *location, ShmemStructOpts *base_options) +{ + ShmemHashOpts *options = (ShmemHashOpts *) base_options; + int hash_flags = options->hash_flags; + HTAB *htab; + + /* attach to it rather than allocate and initialize new space */ + hash_flags |= HASH_ATTACH; + options->hash_info.hctl = location; + Assert(options->hash_info.hctl != NULL); + htab = shmem_hash_create(location, options->base.size, true, + options->name, + options->nelems, &options->hash_info, hash_flags); + + if (options->ptr) + *options->ptr = htab; +} + /* * ShmemInitHash -- Create and initialize, or attach to, a * shared memory hash table. @@ -49,9 +110,8 @@ static void *ShmemHashAlloc(Size size, void *alloc_arg); * to shared-memory hash tables are added here, except that callers may * choose to specify HASH_PARTITION. * - * Note: before Postgres 9.0, this function returned NULL for some failure - * cases. Now, it always throws error instead, so callers need not check - * for NULL. + * Note: This is a legacy interface, kept for backwards compatibility with + * extensions. Use ShmemRequestHash() in new code! */ HTAB * ShmemInitHash(const char *name, /* table string name for shmem index */ @@ -65,7 +125,14 @@ ShmemInitHash(const char *name, /* table string name for shmem index */ size = hash_estimate_size(nelems, infoP->entrysize); - /* look it up in the shmem index or allocate */ + /* + * Look it up in the shmem index or allocate. + * + * NOTE: The area is requested internally as SHMEM_KIND_STRUCT instead of + * SHMEM_KIND_HASH. That's correct because we do the hash table + * initialization by calling shmem_hash_create() ourselves. (We don't + * expose the request kind to users; if we did, that would be confusing.) + */ location = ShmemInitStruct(name, size, &found); return shmem_hash_create(location, size, found, @@ -75,8 +142,8 @@ ShmemInitHash(const char *name, /* table string name for shmem index */ /* * Initialize or attach to a shared hash table in the given shmem region. * - * This is extracted from ShmemInitHash() to allow InitShmemAllocator() to - * share the logic for bootstrapping the ShmemIndex hash table. + * This is exposed to allow InitShmemAllocator() to share the logic for + * bootstrapping the ShmemIndex hash table. */ HTAB * shmem_hash_create(void *location, size_t size, bool found, diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index 5c47cf13473..9b880a6af65 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -121,6 +121,9 @@ FastPathLockShmemSize(void) size = add_size(size, mul_size(TotalProcs, (fpLockBitsSize + fpRelIdSize))); + Assert(TotalProcs > 0); + Assert(size > 0); + return size; } diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 10be60011ad..93851269e43 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -67,6 +67,7 @@ #include "storage/pmsignal.h" #include "storage/proc.h" #include "storage/procsignal.h" +#include "storage/shmem_internal.h" #include "storage/sinval.h" #include "storage/standby.h" #include "tcop/backend_startup.h" @@ -4155,7 +4156,14 @@ PostgresSingleUserMain(int argc, char *argv[], InitializeFastPathLocks(); /* - * Give preloaded libraries a chance to request additional shared memory. + * Before computing the total size needed, give all subsystems, including + * add-ins, a chance to chance to adjust their requested shmem sizes. + */ + ShmemCallRequestCallbacks(); + + /* + * Also call any legacy shmem request hooks that might'be been installed + * by preloaded libraries. */ process_shmem_requests(); diff --git a/src/backend/utils/hash/dynahash.c b/src/backend/utils/hash/dynahash.c index d49a7a92c64..cb95ad4ef2a 100644 --- a/src/backend/utils/hash/dynahash.c +++ b/src/backend/utils/hash/dynahash.c @@ -119,8 +119,8 @@ * chosen at creation based on the initial number of elements, so even though * we support allocating more elements later, performance will suffer if the * table grows much beyond the initial size. (Currently, shared memory hash - * tables are only created by ShmemInitHash() though, which doesn't support - * growing at all.) + * tables are only created by ShmemRequestHash()/ShmemInitHash() though, which + * doesn't support growing at all.) */ #define HASH_SEGSIZE 256 #define HASH_SEGSIZE_SHIFT 8 /* must be log2(HASH_SEGSIZE) */ diff --git a/src/include/storage/shmem.h b/src/include/storage/shmem.h index 81d05381f8f..1680712498f 100644 --- a/src/include/storage/shmem.h +++ b/src/include/storage/shmem.h @@ -28,21 +28,166 @@ #include "utils/hsearch.h" +/* + * Options for ShmemRequestStruct() + * + * 'name' and 'size' are required. Initialize any optional fields that you + * don't use to zeros. + * + * After registration, the shmem machinery reserves memory for the area, sets + * '*ptr' to point to the allocation, and calls the callbacks at the right + * moments. + */ +typedef struct ShmemStructOpts +{ + const char *name; -/* shmem.c */ + /* + * Requested size of the shmem allocation. + * + * When attaching to an existing allocation, the size must match the size + * given when the shmem region was allocated. This cross-check can be + * disabled specifying SHMEM_ATTACH_UNKNOWN_SIZE. + */ + ssize_t size; + + /* + * When the shmem area is initialized or attached to, pointer to it is + * stored in *ptr. It usually points to a global variable, used to access + * the shared memory area later. *ptr is set before the init_fn or + * attach_fn callback is called. + */ + void **ptr; +} ShmemStructOpts; + +#define SHMEM_ATTACH_UNKNOWN_SIZE (-1) + +/* + * Options for ShmemRequestHash() + * + * Each hash table is backed by a contiguous shmem area. + */ +typedef struct ShmemHashOpts +{ + /* Options for allocating the underlying shmem area; do not touch directly */ + ShmemStructOpts base; + + /* + * Name of the shared memory area. Required. Must be unique across the + * system. + */ + const char *name; + + /* + * 'nelems' is the max number of elements for the hash table. + */ + int64 nelems; + + /* + * Hash table options passed to hash_create() + * + * hash_info and hash_flags must specify at least the entry sizes and key + * comparison semantics (see hash_create()). Flag bits and values + * specific to shared-memory hash tables are added implicitly in + * ShmemRequestHash(), except that callers may choose to specify + * HASH_PARTITION and/or HASH_FIXED_SIZE. + */ + HASHCTL hash_info; + int hash_flags; + + /* + * When the hash table is initialized or attached to, pointer to its + * backend-private handle is stored in *ptr. It usually points to a + * global variable, used to access the hash table later. + */ + HTAB **ptr; +} ShmemHashOpts; + +typedef void (*ShmemRequestCallback) (void *opaque_arg); +typedef void (*ShmemInitCallback) (void *opaque_arg); +typedef void (*ShmemAttachCallback) (void *opaque_arg); + +/* + * Shared memory is reserved and allocated in stages at postmaster startup, + * and in EXEC_BACKEND mode, there's some extra work done to "attach" to them + * at backend startup. ShmemCallbacks holds callback functions that are + * called at different stages. + */ +typedef struct ShmemCallbacks +{ + /* SHMEM_CALLBACKS_* flags */ + int flags; + + /* + * 'request_fn' is called during postmaster startup, before the shared + * memory has been allocated. The function should call + * ShmemRequestStruct() and ShmemRequestHash() to register the subsystem's + * shared memory needs. + */ + ShmemRequestCallback request_fn; + + /* + * Initialization callback function. This is called after the shared + * memory area has been allocated, usually at postmaster startup. + */ + ShmemInitCallback init_fn; + + /* + * Attachment callback function. In EXEC_BACKEND mode, this is called at + * startup of each backend. In !EXEC_BACKEND mode, this is only called if + * the shared memory area is registered after postmaster startup (see + * SHMEM_CALLBACKS_ALLOW_AFTER_STARTUP). + */ + ShmemAttachCallback attach_fn; + + /* + * Argument passed to the callbacks. This is opaque to the shmem system, + * callbacks can use it for their own purposes. + */ + void *opaque_arg; +} ShmemCallbacks; + +/* + * Flags to control the behavior of RegisterShmemCallbacks(). + * + * SHMEM_CALLBACKS_ALLOW_AFTER_STARTUP: Normally, calling + * RegisterShmemCallbacks() after postmaster startup, e.g. in an add-in + * library loaded on-demand in a backend, results in an error, because shared + * memory should generally be requested at postmaster startup time. But if + * this flag is set, it is allowed and the callbacks are called immediately to + * initialize or attach to the requested shared memory areas. This is not + * used by any built-in subsystems, but extensions may find it useful. + */ +#define SHMEM_CALLBACKS_ALLOW_AFTER_STARTUP 0x00000001 + +extern void RegisterShmemCallbacks(const ShmemCallbacks *callbacks); +extern bool ShmemAddrIsValid(const void *addr); + +/* + * These macros provide syntactic sugar for calling the underlying functions + * with named arguments -like syntax. + */ +#define ShmemRequestStruct(...) \ + ShmemRequestStructWithOpts(&(ShmemStructOpts){__VA_ARGS__}) + +#define ShmemRequestHash(...) \ + ShmemRequestHashWithOpts(&(ShmemHashOpts){__VA_ARGS__}) + +extern void ShmemRequestStructWithOpts(const ShmemStructOpts *options); +extern void ShmemRequestHashWithOpts(const ShmemHashOpts *options); + +/* legacy shmem allocation functions */ +extern void *ShmemInitStruct(const char *name, Size size, bool *foundPtr); +extern HTAB *ShmemInitHash(const char *name, int64 nelems, + HASHCTL *infoP, int hash_flags); extern void *ShmemAlloc(Size size); extern void *ShmemAllocNoError(Size size); -extern bool ShmemAddrIsValid(const void *addr); -extern void *ShmemInitStruct(const char *name, Size size, bool *foundPtr); + extern Size add_size(Size s1, Size s2); extern Size mul_size(Size s1, Size s2); extern PGDLLIMPORT Size pg_get_shmem_pagesize(void); -/* shmem_hash.c */ -extern HTAB *ShmemInitHash(const char *name, int64 nelems, - HASHCTL *infoP, int hash_flags); - /* ipci.c */ extern void RequestAddinShmemSpace(Size size); diff --git a/src/include/storage/shmem_internal.h b/src/include/storage/shmem_internal.h index e0638135639..9064b86b9a1 100644 --- a/src/include/storage/shmem_internal.h +++ b/src/include/storage/shmem_internal.h @@ -16,26 +16,37 @@ #include "storage/shmem.h" #include "utils/hsearch.h" +/* Different kinds of shmem areas. */ +typedef enum +{ + SHMEM_KIND_STRUCT = 0, /* plain, contiguous area of memory */ + SHMEM_KIND_HASH, /* a hash table */ +} ShmemRequestKind; + +/* shmem.c */ typedef struct PGShmemHeader PGShmemHeader; /* avoid including * storage/pg_shmem.h here */ +extern void ShmemCallRequestCallbacks(void); extern void InitShmemAllocator(PGShmemHeader *seghdr); +#ifdef EXEC_BACKEND +extern void AttachShmemAllocator(PGShmemHeader *seghdr); +#endif +extern void ResetShmemAllocator(void); +extern void ShmemRequestInternal(ShmemStructOpts *options, ShmemRequestKind kind); + +extern size_t ShmemGetRequestedSize(void); +extern void ShmemInitRequested(void); +#ifdef EXEC_BACKEND +extern void ShmemAttachRequested(void); +#endif + +extern PGDLLIMPORT Size pg_get_shmem_pagesize(void); + +/* shmem_hash.c */ extern HTAB *shmem_hash_create(void *location, size_t size, bool found, const char *name, int64 nelems, HASHCTL *infoP, int hash_flags); - -/* size constants for the shmem index table */ - /* max size of data structure string name */ -#define SHMEM_INDEX_KEYSIZE (48) - /* max number of named shmem structures and hash tables */ -#define SHMEM_INDEX_SIZE (256) - -/* this is a hash bucket in the shmem index table */ -typedef struct -{ - char key[SHMEM_INDEX_KEYSIZE]; /* string name */ - void *location; /* location in shared mem */ - Size size; /* # bytes requested for the structure */ - Size allocated_size; /* # bytes actually allocated */ -} ShmemIndexEnt; +extern void shmem_hash_init(void *location, ShmemStructOpts *base_options); +extern void shmem_hash_attach(void *location, ShmemStructOpts *base_options); #endif /* SHMEM_INTERNAL_H */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index ca0c86d9e59..8eea59b62fc 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -2866,9 +2866,14 @@ SharedTypmodTableEntry Sharedsort ShellTypeInfo ShippableCacheEntry -ShmemAllocatorData ShippableCacheKey +ShmemAllocatorData +ShmemCallbacks ShmemIndexEnt +ShmemHashOpts +ShmemRequest +ShmemRequestKind +ShmemStructOpts ShutdownForeignScan_function ShutdownInformation ShutdownMode