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

Generated by: LCOV version 1.16