LCOV - code coverage report
Current view: top level - src/backend/storage/ipc - dsm_registry.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 90.1 % 152 137
Test Date: 2026-04-17 08:16:25 Functions: 100.0 % 7 7
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*-------------------------------------------------------------------------
       2              :  *
       3              :  * dsm_registry.c
       4              :  *    Functions for interfacing with the dynamic shared memory registry.
       5              :  *
       6              :  * This provides a way for libraries to use shared memory without needing
       7              :  * to request it at startup time via a shmem_request_hook.  The registry
       8              :  * stores dynamic shared memory (DSM) segment handles keyed by a
       9              :  * library-specified string.
      10              :  *
      11              :  * The registry is accessed by calling GetNamedDSMSegment().  If a segment
      12              :  * with the provided name does not yet exist, it is created and initialized
      13              :  * with the provided init_callback callback function.  Otherwise,
      14              :  * GetNamedDSMSegment() simply ensures that the segment is attached to the
      15              :  * current backend.  This function guarantees that only one backend
      16              :  * initializes the segment and that all other backends just attach it.
      17              :  *
      18              :  * A DSA can be created in or retrieved from the registry by calling
      19              :  * GetNamedDSA().  As with GetNamedDSMSegment(), if a DSA with the provided
      20              :  * name does not yet exist, it is created.  Otherwise, GetNamedDSA()
      21              :  * ensures the DSA is attached to the current backend.  This function
      22              :  * guarantees that only one backend initializes the DSA and that all other
      23              :  * backends just attach it.
      24              :  *
      25              :  * A dshash table can be created in or retrieved from the registry by
      26              :  * calling GetNamedDSHash().  As with GetNamedDSMSegment(), if a hash
      27              :  * table with the provided name does not yet exist, it is created.
      28              :  * Otherwise, GetNamedDSHash() ensures the hash table is attached to the
      29              :  * current backend.  This function guarantees that only one backend
      30              :  * initializes the table and that all other backends just attach it.
      31              :  *
      32              :  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
      33              :  * Portions Copyright (c) 1994, Regents of the University of California
      34              :  *
      35              :  * IDENTIFICATION
      36              :  *    src/backend/storage/ipc/dsm_registry.c
      37              :  *
      38              :  *-------------------------------------------------------------------------
      39              :  */
      40              : 
      41              : #include "postgres.h"
      42              : 
      43              : #include "funcapi.h"
      44              : #include "lib/dshash.h"
      45              : #include "storage/dsm_registry.h"
      46              : #include "storage/lwlock.h"
      47              : #include "storage/shmem.h"
      48              : #include "storage/subsystems.h"
      49              : #include "utils/builtins.h"
      50              : #include "utils/memutils.h"
      51              : #include "utils/tuplestore.h"
      52              : 
      53              : typedef struct DSMRegistryCtxStruct
      54              : {
      55              :     dsa_handle  dsah;
      56              :     dshash_table_handle dshh;
      57              : } DSMRegistryCtxStruct;
      58              : 
      59              : static DSMRegistryCtxStruct *DSMRegistryCtx;
      60              : 
      61              : static void DSMRegistryShmemRequest(void *arg);
      62              : static void DSMRegistryShmemInit(void *arg);
      63              : 
      64              : const ShmemCallbacks DSMRegistryShmemCallbacks = {
      65              :     .request_fn = DSMRegistryShmemRequest,
      66              :     .init_fn = DSMRegistryShmemInit,
      67              : };
      68              : 
      69              : typedef struct NamedDSMState
      70              : {
      71              :     dsm_handle  handle;
      72              :     size_t      size;
      73              : } NamedDSMState;
      74              : 
      75              : typedef struct NamedDSAState
      76              : {
      77              :     dsa_handle  handle;
      78              :     int         tranche;
      79              : } NamedDSAState;
      80              : 
      81              : typedef struct NamedDSHState
      82              : {
      83              :     dsa_handle dsa_handle;
      84              :     dshash_table_handle dsh_handle;
      85              :     int         tranche;
      86              : } NamedDSHState;
      87              : 
      88              : typedef enum DSMREntryType
      89              : {
      90              :     DSMR_ENTRY_TYPE_DSM,
      91              :     DSMR_ENTRY_TYPE_DSA,
      92              :     DSMR_ENTRY_TYPE_DSH,
      93              : } DSMREntryType;
      94              : 
      95              : static const char *const DSMREntryTypeNames[] =
      96              : {
      97              :     [DSMR_ENTRY_TYPE_DSM] = "segment",
      98              :     [DSMR_ENTRY_TYPE_DSA] = "area",
      99              :     [DSMR_ENTRY_TYPE_DSH] = "hash",
     100              : };
     101              : 
     102              : typedef struct DSMRegistryEntry
     103              : {
     104              :     char        name[NAMEDATALEN];
     105              :     DSMREntryType type;
     106              :     union
     107              :     {
     108              :         NamedDSMState dsm;
     109              :         NamedDSAState dsa;
     110              :         NamedDSHState dsh;
     111              :     };
     112              : } DSMRegistryEntry;
     113              : 
     114              : static const dshash_parameters dsh_params = {
     115              :     offsetof(DSMRegistryEntry, type),
     116              :     sizeof(DSMRegistryEntry),
     117              :     dshash_strcmp,
     118              :     dshash_strhash,
     119              :     dshash_strcpy,
     120              :     LWTRANCHE_DSM_REGISTRY_HASH
     121              : };
     122              : 
     123              : static dsa_area *dsm_registry_dsa;
     124              : static dshash_table *dsm_registry_table;
     125              : 
     126              : static void
     127         1238 : DSMRegistryShmemRequest(void *arg)
     128              : {
     129         1238 :     ShmemRequestStruct(.name = "DSM Registry Data",
     130              :                        .size = sizeof(DSMRegistryCtxStruct),
     131              :                        .ptr = (void **) &DSMRegistryCtx,
     132              :         );
     133         1238 : }
     134              : 
     135              : static void
     136         1235 : DSMRegistryShmemInit(void *arg)
     137              : {
     138         1235 :     DSMRegistryCtx->dsah = DSA_HANDLE_INVALID;
     139         1235 :     DSMRegistryCtx->dshh = DSHASH_HANDLE_INVALID;
     140         1235 : }
     141              : 
     142              : /*
     143              :  * Initialize or attach to the dynamic shared hash table that stores the DSM
     144              :  * registry entries, if not already done.  This must be called before accessing
     145              :  * the table.
     146              :  */
     147              : static void
     148          172 : init_dsm_registry(void)
     149              : {
     150              :     /* Quick exit if we already did this. */
     151          172 :     if (dsm_registry_table)
     152           10 :         return;
     153              : 
     154              :     /* Otherwise, use a lock to ensure only one process creates the table. */
     155          162 :     LWLockAcquire(DSMRegistryLock, LW_EXCLUSIVE);
     156              : 
     157          162 :     if (DSMRegistryCtx->dshh == DSHASH_HANDLE_INVALID)
     158              :     {
     159              :         /* Initialize dynamic shared hash table for registry. */
     160           24 :         dsm_registry_dsa = dsa_create(LWTRANCHE_DSM_REGISTRY_DSA);
     161           24 :         dsm_registry_table = dshash_create(dsm_registry_dsa, &dsh_params, NULL);
     162              : 
     163           24 :         dsa_pin(dsm_registry_dsa);
     164           24 :         dsa_pin_mapping(dsm_registry_dsa);
     165              : 
     166              :         /* Store handles in shared memory for other backends to use. */
     167           24 :         DSMRegistryCtx->dsah = dsa_get_handle(dsm_registry_dsa);
     168           24 :         DSMRegistryCtx->dshh = dshash_get_hash_table_handle(dsm_registry_table);
     169              :     }
     170              :     else
     171              :     {
     172              :         /* Attach to existing dynamic shared hash table. */
     173          138 :         dsm_registry_dsa = dsa_attach(DSMRegistryCtx->dsah);
     174          138 :         dsa_pin_mapping(dsm_registry_dsa);
     175          138 :         dsm_registry_table = dshash_attach(dsm_registry_dsa, &dsh_params,
     176          138 :                                            DSMRegistryCtx->dshh, NULL);
     177              :     }
     178              : 
     179          162 :     LWLockRelease(DSMRegistryLock);
     180              : }
     181              : 
     182              : /*
     183              :  * Initialize or attach a named DSM segment.
     184              :  *
     185              :  * This routine returns the address of the segment.  init_callback is called to
     186              :  * initialize the segment when it is first created.  'arg' is passed through to
     187              :  * the initialization callback function.
     188              :  */
     189              : void *
     190          154 : GetNamedDSMSegment(const char *name, size_t size,
     191              :                    void (*init_callback) (void *ptr, void *arg),
     192              :                    bool *found, void *arg)
     193              : {
     194              :     DSMRegistryEntry *entry;
     195              :     MemoryContext oldcontext;
     196              :     void       *ret;
     197              :     NamedDSMState *state;
     198              :     dsm_segment *seg;
     199              : 
     200              :     Assert(found);
     201              : 
     202          154 :     if (!name || *name == '\0')
     203            0 :         ereport(ERROR,
     204              :                 (errmsg("DSM segment name cannot be empty")));
     205              : 
     206          154 :     if (strlen(name) >= offsetof(DSMRegistryEntry, type))
     207            0 :         ereport(ERROR,
     208              :                 (errmsg("DSM segment name too long")));
     209              : 
     210          154 :     if (size == 0)
     211            0 :         ereport(ERROR,
     212              :                 (errmsg("DSM segment size must be nonzero")));
     213              : 
     214              :     /* Be sure any local memory allocated by DSM/DSA routines is persistent. */
     215          154 :     oldcontext = MemoryContextSwitchTo(TopMemoryContext);
     216              : 
     217              :     /* Connect to the registry. */
     218          154 :     init_dsm_registry();
     219              : 
     220          154 :     entry = dshash_find_or_insert(dsm_registry_table, name, found);
     221          154 :     state = &entry->dsm;
     222          154 :     if (!(*found))
     223              :     {
     224           22 :         entry->type = DSMR_ENTRY_TYPE_DSM;
     225           22 :         state->handle = DSM_HANDLE_INVALID;
     226           22 :         state->size = size;
     227              :     }
     228          132 :     else if (entry->type != DSMR_ENTRY_TYPE_DSM)
     229            0 :         ereport(ERROR,
     230              :                 (errmsg("requested DSM segment does not match type of existing entry")));
     231          132 :     else if (state->size != size)
     232            0 :         ereport(ERROR,
     233              :                 (errmsg("requested DSM segment size does not match size of existing segment")));
     234              : 
     235          154 :     if (state->handle == DSM_HANDLE_INVALID)
     236              :     {
     237           22 :         *found = false;
     238              : 
     239              :         /* Initialize the segment. */
     240           22 :         seg = dsm_create(size, 0);
     241              : 
     242           22 :         if (init_callback)
     243           22 :             (*init_callback) (dsm_segment_address(seg), arg);
     244              : 
     245           22 :         dsm_pin_segment(seg);
     246           22 :         dsm_pin_mapping(seg);
     247           22 :         state->handle = dsm_segment_handle(seg);
     248              :     }
     249              :     else
     250              :     {
     251              :         /* If the existing segment is not already attached, attach it now. */
     252          132 :         seg = dsm_find_mapping(state->handle);
     253          132 :         if (seg == NULL)
     254              :         {
     255          127 :             seg = dsm_attach(state->handle);
     256          127 :             if (seg == NULL)
     257            0 :                 elog(ERROR, "could not map dynamic shared memory segment");
     258              : 
     259          127 :             dsm_pin_mapping(seg);
     260              :         }
     261              :     }
     262              : 
     263          154 :     ret = dsm_segment_address(seg);
     264          154 :     dshash_release_lock(dsm_registry_table, entry);
     265          154 :     MemoryContextSwitchTo(oldcontext);
     266              : 
     267          154 :     return ret;
     268              : }
     269              : 
     270              : /*
     271              :  * Initialize or attach a named DSA.
     272              :  *
     273              :  * This routine returns a pointer to the DSA.  A new LWLock tranche ID will be
     274              :  * generated if needed.  Note that the lock tranche will be registered with the
     275              :  * provided name.  Also note that this should be called at most once for a
     276              :  * given DSA in each backend.
     277              :  */
     278              : dsa_area *
     279           14 : GetNamedDSA(const char *name, bool *found)
     280              : {
     281              :     DSMRegistryEntry *entry;
     282              :     MemoryContext oldcontext;
     283              :     dsa_area   *ret;
     284              :     NamedDSAState *state;
     285              : 
     286              :     Assert(found);
     287              : 
     288           14 :     if (!name || *name == '\0')
     289            0 :         ereport(ERROR,
     290              :                 (errmsg("DSA name cannot be empty")));
     291              : 
     292           14 :     if (strlen(name) >= offsetof(DSMRegistryEntry, type))
     293            0 :         ereport(ERROR,
     294              :                 (errmsg("DSA name too long")));
     295              : 
     296              :     /* Be sure any local memory allocated by DSM/DSA routines is persistent. */
     297           14 :     oldcontext = MemoryContextSwitchTo(TopMemoryContext);
     298              : 
     299              :     /* Connect to the registry. */
     300           14 :     init_dsm_registry();
     301              : 
     302           14 :     entry = dshash_find_or_insert(dsm_registry_table, name, found);
     303           14 :     state = &entry->dsa;
     304           14 :     if (!(*found))
     305              :     {
     306            3 :         entry->type = DSMR_ENTRY_TYPE_DSA;
     307            3 :         state->handle = DSA_HANDLE_INVALID;
     308            3 :         state->tranche = -1;
     309              :     }
     310           11 :     else if (entry->type != DSMR_ENTRY_TYPE_DSA)
     311            0 :         ereport(ERROR,
     312              :                 (errmsg("requested DSA does not match type of existing entry")));
     313              : 
     314           14 :     if (state->tranche == -1)
     315              :     {
     316            3 :         *found = false;
     317              : 
     318              :         /* Initialize the LWLock tranche for the DSA. */
     319            3 :         state->tranche = LWLockNewTrancheId(name);
     320              :     }
     321              : 
     322           14 :     if (state->handle == DSA_HANDLE_INVALID)
     323              :     {
     324            3 :         *found = false;
     325              : 
     326              :         /* Initialize the DSA. */
     327            3 :         ret = dsa_create(state->tranche);
     328            3 :         dsa_pin(ret);
     329            3 :         dsa_pin_mapping(ret);
     330              : 
     331              :         /* Store handle for other backends to use. */
     332            3 :         state->handle = dsa_get_handle(ret);
     333              :     }
     334           11 :     else if (dsa_is_attached(state->handle))
     335            0 :         ereport(ERROR,
     336              :                 (errmsg("requested DSA already attached to current process")));
     337              :     else
     338              :     {
     339              :         /* Attach to existing DSA. */
     340           11 :         ret = dsa_attach(state->handle);
     341           11 :         dsa_pin_mapping(ret);
     342              :     }
     343              : 
     344           14 :     dshash_release_lock(dsm_registry_table, entry);
     345           14 :     MemoryContextSwitchTo(oldcontext);
     346              : 
     347           14 :     return ret;
     348              : }
     349              : 
     350              : /*
     351              :  * Initialize or attach a named dshash table.
     352              :  *
     353              :  * This routine returns the address of the table.  The tranche_id member of
     354              :  * params is ignored; a new LWLock tranche ID will be generated if needed.
     355              :  * Note that the lock tranche will be registered with the provided name.  Also
     356              :  * note that this should be called at most once for a given table in each
     357              :  * backend.
     358              :  */
     359              : dshash_table *
     360            2 : GetNamedDSHash(const char *name, const dshash_parameters *params, bool *found)
     361              : {
     362              :     DSMRegistryEntry *entry;
     363              :     MemoryContext oldcontext;
     364              :     dshash_table *ret;
     365              :     NamedDSHState *dsh_state;
     366              : 
     367              :     Assert(params);
     368              :     Assert(found);
     369              : 
     370            2 :     if (!name || *name == '\0')
     371            0 :         ereport(ERROR,
     372              :                 (errmsg("DSHash name cannot be empty")));
     373              : 
     374            2 :     if (strlen(name) >= offsetof(DSMRegistryEntry, type))
     375            0 :         ereport(ERROR,
     376              :                 (errmsg("DSHash name too long")));
     377              : 
     378              :     /* Be sure any local memory allocated by DSM/DSA routines is persistent. */
     379            2 :     oldcontext = MemoryContextSwitchTo(TopMemoryContext);
     380              : 
     381              :     /* Connect to the registry. */
     382            2 :     init_dsm_registry();
     383              : 
     384            2 :     entry = dshash_find_or_insert(dsm_registry_table, name, found);
     385            2 :     dsh_state = &entry->dsh;
     386            2 :     if (!(*found))
     387              :     {
     388            1 :         entry->type = DSMR_ENTRY_TYPE_DSH;
     389            1 :         dsh_state->dsa_handle = DSA_HANDLE_INVALID;
     390            1 :         dsh_state->dsh_handle = DSHASH_HANDLE_INVALID;
     391            1 :         dsh_state->tranche = -1;
     392              :     }
     393            1 :     else if (entry->type != DSMR_ENTRY_TYPE_DSH)
     394            0 :         ereport(ERROR,
     395              :                 (errmsg("requested DSHash does not match type of existing entry")));
     396              : 
     397            2 :     if (dsh_state->tranche == -1)
     398              :     {
     399            1 :         *found = false;
     400              : 
     401              :         /* Initialize the LWLock tranche for the hash table. */
     402            1 :         dsh_state->tranche = LWLockNewTrancheId(name);
     403              :     }
     404              : 
     405            2 :     if (dsh_state->dsa_handle == DSA_HANDLE_INVALID)
     406              :     {
     407              :         dshash_parameters params_copy;
     408              :         dsa_area   *dsa;
     409              : 
     410            1 :         *found = false;
     411              : 
     412              :         /* Initialize the DSA for the hash table. */
     413            1 :         dsa = dsa_create(dsh_state->tranche);
     414              : 
     415              :         /* Initialize the dshash table. */
     416            1 :         memcpy(&params_copy, params, sizeof(dshash_parameters));
     417            1 :         params_copy.tranche_id = dsh_state->tranche;
     418            1 :         ret = dshash_create(dsa, &params_copy, NULL);
     419              : 
     420            1 :         dsa_pin(dsa);
     421            1 :         dsa_pin_mapping(dsa);
     422              : 
     423              :         /* Store handles for other backends to use. */
     424            1 :         dsh_state->dsa_handle = dsa_get_handle(dsa);
     425            1 :         dsh_state->dsh_handle = dshash_get_hash_table_handle(ret);
     426              :     }
     427            1 :     else if (dsa_is_attached(dsh_state->dsa_handle))
     428            0 :         ereport(ERROR,
     429              :                 (errmsg("requested DSHash already attached to current process")));
     430              :     else
     431              :     {
     432              :         dsa_area   *dsa;
     433              : 
     434              :         /* XXX: Should we verify params matches what table was created with? */
     435              : 
     436              :         /* Attach to existing DSA for the hash table. */
     437            1 :         dsa = dsa_attach(dsh_state->dsa_handle);
     438            1 :         dsa_pin_mapping(dsa);
     439              : 
     440              :         /* Attach to existing dshash table. */
     441            1 :         ret = dshash_attach(dsa, params, dsh_state->dsh_handle, NULL);
     442              :     }
     443              : 
     444            2 :     dshash_release_lock(dsm_registry_table, entry);
     445            2 :     MemoryContextSwitchTo(oldcontext);
     446              : 
     447            2 :     return ret;
     448              : }
     449              : 
     450              : Datum
     451            2 : pg_get_dsm_registry_allocations(PG_FUNCTION_ARGS)
     452              : {
     453            2 :     ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
     454              :     DSMRegistryEntry *entry;
     455              :     MemoryContext oldcontext;
     456              :     dshash_seq_status status;
     457              : 
     458            2 :     InitMaterializedSRF(fcinfo, MAT_SRF_USE_EXPECTED_DESC);
     459              : 
     460              :     /* Be sure any local memory allocated by DSM/DSA routines is persistent. */
     461            2 :     oldcontext = MemoryContextSwitchTo(TopMemoryContext);
     462            2 :     init_dsm_registry();
     463            2 :     MemoryContextSwitchTo(oldcontext);
     464              : 
     465            2 :     dshash_seq_init(&status, dsm_registry_table, false);
     466            5 :     while ((entry = dshash_seq_next(&status)) != NULL)
     467              :     {
     468              :         Datum       vals[3];
     469            3 :         bool        nulls[3] = {0};
     470              : 
     471            3 :         vals[0] = CStringGetTextDatum(entry->name);
     472            3 :         vals[1] = CStringGetTextDatum(DSMREntryTypeNames[entry->type]);
     473              : 
     474              :         /* Be careful to only return the sizes of initialized entries. */
     475            3 :         if (entry->type == DSMR_ENTRY_TYPE_DSM &&
     476            1 :             entry->dsm.handle != DSM_HANDLE_INVALID)
     477            1 :             vals[2] = Int64GetDatum(entry->dsm.size);
     478            2 :         else if (entry->type == DSMR_ENTRY_TYPE_DSA &&
     479            1 :                  entry->dsa.handle != DSA_HANDLE_INVALID)
     480            1 :             vals[2] = Int64GetDatum(dsa_get_total_size_from_handle(entry->dsa.handle));
     481            1 :         else if (entry->type == DSMR_ENTRY_TYPE_DSH &&
     482            1 :                  entry->dsh.dsa_handle !=DSA_HANDLE_INVALID)
     483            1 :             vals[2] = Int64GetDatum(dsa_get_total_size_from_handle(entry->dsh.dsa_handle));
     484              :         else
     485            0 :             nulls[2] = true;
     486              : 
     487            3 :         tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, vals, nulls);
     488              :     }
     489            2 :     dshash_seq_term(&status);
     490              : 
     491            2 :     return (Datum) 0;
     492              : }
        

Generated by: LCOV version 2.0-1