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

Generated by: LCOV version 1.16