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

Generated by: LCOV version 1.16