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

Generated by: LCOV version 2.0-1