From 93e390cb87d3dc341b6c7d695570c3f834739bcf Mon Sep 17 00:00:00 2001 From: Heikki Linnakangas Date: Mon, 16 Mar 2026 20:08:25 +0200 Subject: [PATCH v5 3/5] 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 Discussion: https://www.postgresql.org/message-id/CAExHW5vM1bneLYfg0wGeAa=52UiJ3z4vKd3AJ72X8Fw6k3KKrg@mail.gmail.com --- doc/src/sgml/system-views.sgml | 4 +- doc/src/sgml/xfunc.sgml | 126 ++-- src/backend/bootstrap/bootstrap.c | 2 + src/backend/postmaster/launch_backend.c | 4 + src/backend/postmaster/postmaster.c | 11 +- src/backend/storage/ipc/ipci.c | 50 +- src/backend/storage/ipc/shmem.c | 735 +++++++++++++++++++----- src/backend/tcop/postgres.c | 3 + src/include/storage/ipc.h | 1 + src/include/storage/shmem.h | 153 ++++- src/tools/pgindent/typedefs.list | 4 +- 11 files changed, 863 insertions(+), 230 deletions(-) diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml index e5fe423fc61..a0baed339fe 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(). + ShmemRegisterStruct() or + ShmemRegisterHash(). diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml index 70e815b8a2c..05d9ec7e33a 100644 --- a/doc/src/sgml/xfunc.sgml +++ b/doc/src/sgml/xfunc.sgml @@ -3628,59 +3628,87 @@ 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: - -void RequestAddinShmemSpace(Size size) - - Each backend should obtain a pointer to the reserved shared memory by - calling: + The shared library should register the shared memory allocation in + its _PG_init function. Here is an example: -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_init(void *arg); + +static ShmemStructDesc MyShmemDesc = { + .name = "My shmem area", + .size = sizeof(MyShmemData), + .init_fn = my_shmem_init, + .ptr = (void **) &MyShmem, +}; -LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE); -ptr = ShmemInitStruct("my struct name", size, &found); -if (!found) +/* + * Module load callback + */ +void +_PG_init(void) { - ... initialize contents of shared memory ... - ptr->locks = GetNamedLWLockTranche("my tranche name"); + /* + * 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 */ + ShmemRegisterStruct(&MyShmemDesc); } -LWLockRelease(AddinShmemInitLock); + +/* 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 ... +} + - 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 ShmemRegisterStruct() call doesn't immediately + allocate or initialize the memory, it merely registers the space to be + allocated later in the startup sequence. If the size of the allocation + depends on MaxBackends or other variables that are + not yet initialized when _PG_init() is called, the + size can still be adjusted later by registering a + shmem_request_hook and changing the descriptor there. + When the memory is allocated, the registered + init_fn callback is called to initialize it. + + + The init_fn() callback is normally called at + postmaster startup, when no other processes are running yet and no + locking is required. However, if a shared memory area is registered + after system start, e.g. in an extension that is not in + shared_preload_libraries, + ShmemRegisterStruct() will immediately call + the init_fn callback. In that case, it holds a + lock internally that prevents concurrent shmem allocations. + + + On Windows, the attach_fn callback is additionally + called at every backend startup. It can be used for initializing + additional per-backend state related to the shared memory area that is + inherited via fork() on other systems. On other + platforms, the attach_fn callback is only called + for structs that are registered after system startup. - - An example of a shmem_request_hook and - shmem_startup_hook can be found in + An example of allocating shared memory can be found in contrib/pg_stat_statements/pg_stat_statements.c in the PostgreSQL source tree. @@ -3691,8 +3719,7 @@ LWLockRelease(AddinShmemInitLock); 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 +3738,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 17118f2fe76..d9d8bb6a5fd 100644 --- a/src/backend/bootstrap/bootstrap.c +++ b/src/backend/bootstrap/bootstrap.c @@ -369,6 +369,8 @@ BootstrapModeMain(int argc, char *argv[], bool check_only) InitializeFastPathLocks(); + RegisterShmemStructs(); + CreateSharedMemoryAndSemaphores(); /* diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c index 30357845729..fecae827e5b 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" @@ -677,7 +678,10 @@ SubPostmasterMain(int argc, char *argv[]) /* Restore basic shared memory pointers */ if (UsedShmemSegAddr != NULL) + { InitShmemAllocator(UsedShmemSegAddr); + RegisterShmemStructs(); + } /* * Run the appropriate Main function diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 3fac46c402b..a3be3bbe3b6 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -958,6 +958,9 @@ PostmasterMain(int argc, char *argv[]) */ InitializeFastPathLocks(); + /* Register the shared memory needs of all core subsystems. */ + RegisterShmemStructs(); + /* * Give preloaded libraries a chance to request additional shared memory. */ @@ -3235,7 +3238,13 @@ 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(); CreateSharedMemoryAndSemaphores(); UpdatePMState(PM_STARTUP); diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c index 3d3f153809b..405c69655f0 100644 --- a/src/backend/storage/ipc/ipci.c +++ b/src/backend/storage/ipc/ipci.c @@ -99,10 +99,12 @@ 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, ShmemRegisteredSize()); + size = add_size(size, dsm_estimate_size()); size = add_size(size, DSMRegistryShmemSize()); + + /* legacy subsystems */ size = add_size(size, BufferManagerShmemSize()); size = add_size(size, LockManagerShmemSize()); size = add_size(size, PredicateLockShmemSize()); @@ -218,6 +220,10 @@ CreateSharedMemoryAndSemaphores(void) */ InitShmemAllocator(seghdr); + /* Reserve space for semaphores. */ + if (!IsUnderPostmaster) + PGReserveSemaphores(ProcGlobalSemas()); + /* Initialize subsystems */ CreateOrAttachShmemStructs(); @@ -231,6 +237,22 @@ CreateSharedMemoryAndSemaphores(void) shmem_startup_hook(); } +/* + * Early initialization of various subsystems, giving them a chance to + * register their shared memory needs before the shared memory segment is + * allocated. + */ +void +RegisterShmemStructs(void) +{ + /* + * TODO: Not used in any built-in subsystems yet. In the future, most of + * the calls *ShmemInit() calls in CreateOrAttachShmemStructs(), and + * *ShmemSize() calls in CalculateShmemSize() will be replaced by calls + * into the subsystems from here. + */ +} + /* * Initialize various subsystems, setting up their data structures in * shared memory. @@ -249,10 +271,26 @@ CreateSharedMemoryAndSemaphores(void) static void CreateOrAttachShmemStructs(void) { - /* - * Now initialize LWLocks, which do shared memory allocation. - */ - CreateLWLocks(); +#ifdef EXEC_BACKEND + if (IsUnderPostmaster) + { + /* + * ShmemAttachRegistered() uses LWLocks. Fortunately, LWLocks don't + * need any special attaching. + */ + ShmemAttachRegistered(); + } + else +#endif + { + /* + * 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.) + */ + CreateLWLocks(); + ShmemInitRegistered(); + } dsm_shmem_init(); DSMRegistryShmemInit(); diff --git a/src/backend/storage/ipc/shmem.c b/src/backend/storage/ipc/shmem.c index b3f1262a497..1f999ddb5d9 100644 --- a/src/backend/storage/ipc/shmem.c +++ b/src/backend/storage/ipc/shmem.c @@ -19,48 +19,101 @@ * 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. + * Two kinds of shared memory data structures are handled by this module: + * fixed-size structures and hash tables. Fixed-size structures contain + * things like variables shared between all backend processes. 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 the + * descriptor when its registered. + * + * Shared memory structs and hash tables should 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 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 a shared memory area, fill in the name, size, and any other + * options in ShmemStructDesc, and call ShmemRegisterStruct(). Leave any + * unused fields as zeros. + * + * typedef struct MyShmemData { + * ... + * } MyShmemData; + * + * static MyShmemData *MyShmem; + * + * static void my_shmem_init(void *arg); + * + * static ShmemStructDesc MyShmemDesc = { + * .name = "My shmem area", + * .size = sizeof(MyShmemData), + * .init_fn = my_shmem_init, + * .ptr = &MyShmem, + * }; + * + * In the subsystem's initialization code (or in _PG_init() in extensions), + * call ShmemRegisterStruct(&MyShmemDesc). + * + * Lifecycle + * --------- + * + * RegisterShmemStructs() is called at postmaster startup before calculating + * the size of the global shared memory segment. Once all the registrations + * have been done, postmaster calls ShmemRegisteredSize() to add up the sizes + * of all the registered areas. After allocating the shared memory segment, + * postmaster calls ShmemInitRegistered(), which calls the init_fn callback, + * if any, of each registered area, in the order that they were registered. + * + * 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 RegisterShmemStructs(), followed by + * ShmemAttachRegistered(), which re-establishes 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 registring shmem areas. + * It pre-dates the ShmemRegisterStruct()/ShmemRegisterHash() 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" @@ -76,6 +129,28 @@ #include "storage/spin.h" #include "utils/builtins.h" +/* + * Array of registered shared memory areas. + * + * 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(). Extensions may register additional + * areas after startup, but only areas registered at postmaster startup are + * included in the estimate for the total memory needed for shared memory. If + * any non-trivial allocations are made after startup, there might not be + * enough shared memory available. + */ +static struct +{ + ShmemStructDesc *desc; /* registered descriptor */ + bool legacy; /* legacy ShmemInitStruct/Hash entry? */ +} *registered_shmem_areas; +static int num_registered_shmem_areas = 0; +static int max_registered_shmem_areas = 0; /* allocated size of the array */ + +/* estimated size of registered_shmem_areas (not a hard limit) */ +#define INITIAL_REGISTRY_SIZE (64) + /* * This is the first data structure stored in the shared memory segment, at * the offset that PGShmemHeader->content_offset points to. Allocations by @@ -93,8 +168,13 @@ typedef struct ShmemAllocatorData slock_t shmem_lock; } ShmemAllocatorData; +static bool ShmemRegisterStructInternal(ShmemStructDesc *desc, bool legacy); +static bool ShmemRegisterHashInternal(ShmemHashDesc *desc, bool legacy); static void *ShmemAllocRaw(Size size, Size *allocated_size); +static void shmem_hash_init(void *arg); +static void shmem_hash_attach(void *arg); + /* shared memory global variables */ static PGShmemHeader *ShmemSegHdr; /* shared mem segment header */ @@ -103,20 +183,332 @@ static void *ShmemEnd; /* end+1 address of shared memory */ static ShmemAllocatorData *ShmemAllocator; slock_t *ShmemLock; /* points to ShmemAllocator->shmem_lock */ -static HTAB *ShmemIndex = NULL; /* primary index hashtable for shmem */ + +static bool shmem_initialized = false; + +/* + * 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 (not a hard limit) + */ +#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; Datum pg_numa_available(PG_FUNCTION_ARGS); +/* + * ShmemRegisterStruct() --- register a shared memory struct + * + * Subsystems call this to register their shared memory needs. That should be + * 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. + * + * In core subsystems, each subsystem's registration function is called from + * RegisterShmemStructs(). In extensions, this should be called from the + * _PG_init() function. In EXEC_BACKEND mode, this also needs to be called in + * each child process, to reattach and set the pointer to the shared memory + * area, usually in a global variable. Calling this from the _PG_init() + * initializer takes care of that too. + * + * When called during postmaster startup, before the shared memory has been + * allocated, the function merely remembers the registered descriptor, but the + * descriptor may still be changed later, until the shared memory segment has + * been allocated. That means that an extension may still modify the + * already-registered descriptor in the shmem_request_hook. A common example + * of when that's useful is when the size depends on MaxBackends: you can + * leave the size empty in the ShmemRegisterStruct() call and fill it later in + * the shmem_request_hook. + * + * Returns true if the struct was already initialized in shared memory and we + * merely attached to it. + */ +bool +ShmemRegisterStruct(ShmemStructDesc *desc) +{ + return ShmemRegisterStructInternal(desc, false); +} + +static bool +ShmemRegisterStructInternal(ShmemStructDesc *desc, bool legacy) +{ + bool found; + + /* Check that it's not already registered in this process */ + for (int i = 0; i < num_registered_shmem_areas; i++) + { + ShmemStructDesc *existing = registered_shmem_areas[i].desc; + + if (strcmp(existing->name, desc->name) == 0) + ereport(ERROR, + (errmsg("shared memory struct \"%s\" is already registered", + desc->name), + errbacktrace())); + } + + /* desc->ptr can be non-NULL when re-initializing after crash */ + if (!IsUnderPostmaster && desc->ptr) + *desc->ptr = NULL; + + /* Add the descriptor to the array, growing the array if needed */ + if (num_registered_shmem_areas == max_registered_shmem_areas) + { + int new_size; + + if (registered_shmem_areas) + { + new_size = max_registered_shmem_areas * 2; + registered_shmem_areas = repalloc(registered_shmem_areas, + new_size * sizeof(*registered_shmem_areas)); + } + else + { + new_size = INITIAL_REGISTRY_SIZE; + registered_shmem_areas = MemoryContextAlloc(TopMemoryContext, + new_size * sizeof(*registered_shmem_areas)); + } + max_registered_shmem_areas = new_size; + } + registered_shmem_areas[num_registered_shmem_areas].desc = desc; + registered_shmem_areas[num_registered_shmem_areas].legacy = legacy; + num_registered_shmem_areas++; + + /* + * If called after postmaster startup, we need to immediately also + * initialize or attach to the area. + */ + if (shmem_initialized) + { + ShmemIndexEnt *index_entry; + + LWLockAcquire(ShmemIndexLock, LW_EXCLUSIVE); + + /* look it up in the shmem index */ + index_entry = (ShmemIndexEnt *) + hash_search(ShmemIndex, desc->name, HASH_ENTER_NULL, &found); + if (!index_entry) + { + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("could not create ShmemIndex entry for data structure \"%s\"", + desc->name))); + } + if (found) + { + /* Already present, just attach to it */ + if (index_entry->size != desc->size) + elog(ERROR, "shared memory struct \"%s\" is already registered with different size", + desc->name); + if (desc->ptr) + *desc->ptr = index_entry->location; + if (desc->attach_fn) + desc->attach_fn(desc->attach_fn_arg); + } + else + { + /* + * This is the first time. Initialize it like + * ShmemInitRegistered() would + */ + size_t allocated_size; + void *structPtr; + + structPtr = ShmemAllocRaw(desc->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, desc->size))); + } + index_entry->size = desc->size; + index_entry->allocated_size = allocated_size; + index_entry->location = structPtr; + if (desc->ptr) + *desc->ptr = index_entry->location; + + /* + * XXX: if this errors out, the area is left in a half-initialized + * state + */ + if (desc->init_fn) + desc->init_fn(desc->init_fn_arg); + } + + LWLockRelease(ShmemIndexLock); + } + else + found = false; + + return found; +} + +/* + * ShmemRegisteredSize() --- 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 +ShmemRegisteredSize(void) +{ + size_t size; + + /* memory needed for the ShmemIndex */ + size = hash_estimate_size(num_registered_shmem_areas + SHMEM_INDEX_ADDITIONAL_SIZE, + sizeof(ShmemIndexEnt)); + + /* memory needed for all the registered areas */ + for (int i = 0; i < num_registered_shmem_areas; i++) + { + ShmemStructDesc *desc = registered_shmem_areas[i].desc; + + size = add_size(size, desc->size); + size = add_size(size, desc->extra_size); + } + + return size; +} + +/* + * ShmemInitRegistered() --- allocate and initialize pre-registered shared + * memory structures. + * + * This is called once at postmaster startup, after the shared memory segment + * has been created. + */ +void +ShmemInitRegistered(void) +{ + /* Should be called only by the postmaster or a standalone backend. */ + Assert(!IsUnderPostmaster); + Assert(!shmem_initialized); + + /* + * Initialize all the registered memory areas. There are no concurrent + * processes yet, so no need for locking. + */ + for (int i = 0; i < num_registered_shmem_areas; i++) + { + ShmemStructDesc *desc = registered_shmem_areas[i].desc; + size_t allocated_size; + void *structPtr; + bool found; + ShmemIndexEnt *index_entry; + + /* look it up in the shmem index */ + index_entry = (ShmemIndexEnt *) + hash_search(ShmemIndex, desc->name, HASH_ENTER_NULL, &found); + if (!index_entry) + { + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("could not create ShmemIndex entry for data structure \"%s\"", + desc->name))); + } + if (found) + elog(ERROR, "shared memory struct \"%s\" is already initialized", desc->name); + + /* allocate and initialize it */ + structPtr = ShmemAllocRaw(desc->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, desc->size))); + } + index_entry->size = desc->size; + index_entry->allocated_size = allocated_size; + index_entry->location = structPtr; + + *(desc->ptr) = structPtr; + if (desc->init_fn) + desc->init_fn(desc->init_fn_arg); + } + + shmem_initialized = true; +} + +/* + * Call the attach_fn callbacks of all registered shmem areas + * + * This is called at backend startup, in EXEC_BACKEND mode. + */ +#ifdef EXEC_BACKEND +void +ShmemAttachRegistered(void) +{ + /* Must be initializing a (non-standalone) backend */ + Assert(IsUnderPostmaster); + Assert(ShmemAllocator->index != NULL); + + LWLockAcquire(ShmemIndexLock, LW_SHARED); + + for (int i = 0; i < num_registered_shmem_areas; i++) + { + ShmemStructDesc *desc = registered_shmem_areas[i].desc; + bool found; + ShmemIndexEnt *result; + + /* look it up in the shmem index */ + result = (ShmemIndexEnt *) + hash_search(ShmemIndex, desc->name, HASH_FIND, &found); + if (!found) + { + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("could not find ShmemIndex entry for data structure \"%s\"", + desc->name))); + } + + if (desc->ptr) + *desc->ptr = result->location; + if (desc->attach_fn) + desc->attach_fn(desc->attach_fn_arg); + } + + LWLockRelease(ShmemIndexLock); + + shmem_initialized = true; +} +#endif + /* * 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) @@ -127,6 +519,7 @@ InitShmemAllocator(PGShmemHeader *seghdr) int hash_flags; size_t size; + Assert(!shmem_initialized); Assert(seghdr != NULL); /* @@ -171,7 +564,7 @@ InitShmemAllocator(PGShmemHeader *seghdr) * use ShmemInitHash() here because it relies on ShmemIndex being already * initialized. */ - hash_size = SHMEM_INDEX_SIZE; + hash_size = num_registered_shmem_areas + SHMEM_INDEX_ADDITIONAL_SIZE; info.keysize = SHMEM_INDEX_KEYSIZE; info.entrysize = sizeof(ShmemIndexEnt); @@ -190,6 +583,38 @@ InitShmemAllocator(PGShmemHeader *seghdr) Assert(ShmemIndex != NULL); } +/* + * Reset the shmem struct registry on postmaster crash restart. + */ +void +ResetShmemAllocator(void) +{ + int num_retained; + + shmem_initialized = false; + + /* + * Shared memory areas will not be registered again after a crash restart. + * We don't call RegisterShmemStructs() on crash restart, which would + * re-register core subsystems, and we don't reload + * shared_preload_libraries either. + * + * However, we do expect the legacy ShmemInitStruct() function will be + * called again for each area, so remove those from the registry. + */ + num_retained = 0; + for (int i = 0; i < num_registered_shmem_areas; i++) + { + if (!registered_shmem_areas[i].legacy) + { + if (num_retained != i) + registered_shmem_areas[num_retained] = registered_shmem_areas[i]; + num_retained++; + } + } + num_registered_shmem_areas = num_retained; +} + /* * ShmemAlloc -- allocate max-aligned chunk from shared memory * @@ -287,42 +712,20 @@ ShmemAddrIsValid(const void *addr) } /* - * ShmemInitHash -- Create and initialize, or attach to, a - * shared memory hash table. + * ShmemRegisterHash -- Register a shared memory hash table. * - * We assume caller is doing some kind of synchronization - * so that two processes don't try to create/initialize the same - * table at once. (In practice, all creations are done in the postmaster - * process; child processes should always be attaching to existing tables.) - * - * 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). - * - * 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. - * - * *infoP 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 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. + * Similar to ShmemRegisterStruct(), but registers a hash table instead of an + * opaque area. */ -HTAB * -ShmemInitHash(const char *name, /* table string name for shmem index */ - int64 init_size, /* initial table size */ - int64 max_size, /* max size of the table */ - HASHCTL *infoP, /* info about key and bucket size */ - int hash_flags) /* info about infoP */ +bool +ShmemRegisterHash(ShmemHashDesc *desc) { - bool found; - void *location; + return ShmemRegisterHashInternal(desc, false); +} +static bool +ShmemRegisterHashInternal(ShmemHashDesc *desc, bool legacy) +{ /* * 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 @@ -330,26 +733,56 @@ ShmemInitHash(const char *name, /* table string name for shmem index */ * * The shared memory allocator must be specified too. */ - infoP->dsize = infoP->max_dsize = hash_select_dirsize(max_size); - infoP->alloc = ShmemAllocNoError; - hash_flags |= HASH_SHARED_MEM | HASH_ALLOC | HASH_DIRSIZE; - - /* look it up in the shmem index */ - location = ShmemInitStruct(name, - hash_get_shared_size(infoP, hash_flags), - &found); + desc->hash_info.dsize = desc->hash_info.max_dsize = hash_select_dirsize(desc->max_size); + desc->hash_info.alloc = ShmemAllocNoError; + desc->hash_flags |= HASH_SHARED_MEM | HASH_ALLOC | HASH_DIRSIZE; + + /* Set up the base struct descriptor */ + memset(&desc->base_desc, 0, sizeof(desc->base_desc)); + desc->base_desc.name = desc->name; + desc->base_desc.size = hash_get_shared_size(&desc->hash_info, desc->hash_flags); + desc->base_desc.init_fn = shmem_hash_init; + desc->base_desc.init_fn_arg = desc; + desc->base_desc.attach_fn = shmem_hash_attach; + desc->base_desc.attach_fn_arg = desc; /* - * if it already exists, attach to it rather than allocate and initialize - * new space + * We need a stable pointer to hold the pointer to the shared memory. Use + * the one passed in the descriptor now. It will be replaced with the + * hash table header by init or attach function. */ - if (found) - hash_flags |= HASH_ATTACH; + desc->base_desc.ptr = (void **) desc->ptr; + + desc->base_desc.extra_size = hash_estimate_size(desc->max_size, desc->hash_info.entrysize) - desc->base_desc.size; + + return ShmemRegisterStructInternal(&desc->base_desc, legacy); +} + +static void +shmem_hash_init(void *arg) +{ + ShmemHashDesc *desc = (ShmemHashDesc *) arg; + int hash_flags = desc->hash_flags; /* Pass location of hashtable header to hash_create */ - infoP->hctl = (HASHHDR *) location; + desc->hash_info.hctl = (HASHHDR *) *desc->base_desc.ptr; - return hash_create(name, init_size, infoP, hash_flags); + *desc->ptr = hash_create(desc->name, desc->init_size, &desc->hash_info, hash_flags); +} + +static void +shmem_hash_attach(void *arg) +{ + ShmemHashDesc *desc = (ShmemHashDesc *) arg; + int hash_flags = desc->hash_flags; + + /* attach to it rather than allocate and initialize new space */ + hash_flags |= HASH_ATTACH; + + /* Pass location of hashtable header to hash_create */ + desc->hash_info.hctl = (HASHHDR *) *desc->base_desc.ptr; + + *desc->ptr = hash_create(desc->name, desc->init_size, &desc->hash_info, hash_flags); } /* @@ -364,82 +797,76 @@ ShmemInitHash(const char *name, /* table string name for shmem index */ * 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 ShmemRegisterStruct() in new code! */ void * ShmemInitStruct(const char *name, Size size, bool *foundPtr) { - ShmemIndexEnt *result; - void *structPtr; - - Assert(ShmemIndex != NULL); + ShmemStructDesc *desc; - LWLockAcquire(ShmemIndexLock, LW_EXCLUSIVE); + Assert(shmem_initialized); - /* look it up in the shmem index */ - result = (ShmemIndexEnt *) - hash_search(ShmemIndex, name, HASH_ENTER_NULL, foundPtr); + desc = MemoryContextAllocZero(TopMemoryContext, sizeof(ShmemStructDesc) + sizeof(void *)); + desc->name = name; + desc->size = size; + desc->ptr = (void *) (((char *) desc) + sizeof(ShmemStructDesc)); - 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; + *foundPtr = ShmemRegisterStructInternal(desc, true); + Assert(*desc->ptr != NULL); + return *desc->ptr; +} - /* 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; - } +/* + * ShmemInitHash -- Create and initialize, or attach to, a + * shared memory hash table. + * + * We assume caller is doing some kind of synchronization + * so that two processes don't try to create/initialize the same + * table at once. (In practice, all creations are done in the postmaster + * process; child processes should always be attaching to existing tables.) + * + * 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). + * + * 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. + * + * *infoP 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 here, except that callers may + * choose to specify HASH_PARTITION and/or HASH_FIXED_SIZE. + * + * Note: This is a legacy interface, kept for backwards compatibility with + * extensions. Use ShmemRegisterHash() in new code! + */ +HTAB * +ShmemInitHash(const char *name, /* table string name for shmem index */ + int64 init_size, /* initial table size */ + int64 max_size, /* max size of the table */ + HASHCTL *infoP, /* info about key and bucket size */ + int hash_flags) /* info about infoP */ +{ + ShmemHashDesc *desc; - LWLockRelease(ShmemIndexLock); + Assert(shmem_initialized); - Assert(ShmemAddrIsValid(structPtr)); + desc = MemoryContextAllocZero(TopMemoryContext, sizeof(ShmemHashDesc) + sizeof(HTAB *)); + desc->name = name; + desc->init_size = init_size; + desc->max_size = max_size; + memcpy(&desc->hash_info, infoP, sizeof(HASHCTL)); + desc->hash_flags = hash_flags; - Assert(structPtr == (void *) CACHELINEALIGN(structPtr)); + desc->ptr = (HTAB **) (((char *) desc) + sizeof(ShmemHashDesc)); - return structPtr; + ShmemRegisterHashInternal(desc, true); + return *desc->ptr; } - /* * Add two Size values, checking for overflow */ diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index d01a09dd0c4..fa074c419a8 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -4162,6 +4162,9 @@ PostgresSingleUserMain(int argc, char *argv[], /* Initialize size of fast-path lock cache. */ InitializeFastPathLocks(); + /* Register the shared memory needs of all core subsystems. */ + RegisterShmemStructs(); + /* * Give preloaded libraries a chance to request additional shared memory. */ diff --git a/src/include/storage/ipc.h b/src/include/storage/ipc.h index da32787ab51..8a3b71ad5d3 100644 --- a/src/include/storage/ipc.h +++ b/src/include/storage/ipc.h @@ -77,6 +77,7 @@ extern void check_on_shmem_exit_lists_are_empty(void); /* ipci.c */ extern PGDLLIMPORT shmem_startup_hook_type shmem_startup_hook; +extern void RegisterShmemStructs(void); extern Size CalculateShmemSize(void); extern void CreateSharedMemoryAndSemaphores(void); #ifdef EXEC_BACKEND diff --git a/src/include/storage/shmem.h b/src/include/storage/shmem.h index 0de8a36429b..08aa6380a43 100644 --- a/src/include/storage/shmem.h +++ b/src/include/storage/shmem.h @@ -24,18 +24,156 @@ #include "storage/spin.h" #include "utils/hsearch.h" +typedef void (*ShmemInitCallback) (void *arg); +typedef void (*ShmemAttachCallback) (void *arg); + +/* + * ShmemStructDesc describes a named area or struct in shared memory. + * + * Shared memory is reserved and allocated in a few phases at postmaster + * startup, and in EXEC_BACKEND mode, there's some extra work done to "attach" + * to them at backend startup. ShmemStructDesc contains all the information + * needed to manage the lifecycle. + * + * 'name' must be filled in before calling ShmemRegisterStruct(); all other + * fields can be adjusted later during postmaster startup, until the shared + * memory is allocated and init callback is called. 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 of the shared memory area. Required, must be unique, and must be + * set already before calling ShmemRegisterStruct(). + */ + const char *name; + + /* Size of the shared memory area. Required. */ + size_t size; + + /* + * Initialization callback function. This is called when the shared + * memory area is allocated, usually at postmaster startup. 'init_fn_arg' + * is an opaque argument passed to the callback. + */ + 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. We + * never do that in core code, but extensions might. + */ + ShmemInitCallback attach_fn; + void *attach_fn_arg; + + /* + * 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; +} ShmemStructDesc; + +/* + * 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 ShmemHashDesc +{ + /* + * 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 + * ShmemRegisterHash(), 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; + + /* + * Descriptor for the underlying "area". Callers of ShmemRegisterHash() + * do not need to touch this, it is filled in by ShmemRegisterHash() based + * on the hash table parameters. + */ + ShmemStructDesc base_desc; +} ShmemHashDesc; /* shmem.c */ extern PGDLLIMPORT slock_t *ShmemLock; 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 bool ShmemRegisterStruct(ShmemStructDesc *desc); +extern bool ShmemRegisterHash(ShmemHashDesc *desc); + +/* 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 ShmemRegisteredSize(void); +extern void ShmemInitRegistered(void); +#ifdef EXEC_BACKEND +extern void ShmemAttachRegistered(void); +#endif + extern Size add_size(Size s1, Size s2); extern Size mul_size(Size s1, Size s2); @@ -44,19 +182,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/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 52f8603a7be..f4c8cfc707d 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -2835,9 +2835,11 @@ SharedTypmodTableEntry Sharedsort ShellTypeInfo ShippableCacheEntry -ShmemAllocatorData ShippableCacheKey +ShmemAllocatorData ShmemIndexEnt +ShmemHashDesc +ShmemStructDesc ShutdownForeignScan_function ShutdownInformation ShutdownMode -- 2.47.3