|
| 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 | + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group |
| 19 | + * Portions Copyright (c) 1994, Regents of the University of California |
| 20 | + * |
| 21 | + * IDENTIFICATION |
| 22 | + * src/backend/storage/ipc/dsm_registry.c |
| 23 | + * |
| 24 | + *------------------------------------------------------------------------- |
| 25 | + */ |
| 26 | + |
| 27 | +#include "postgres.h" |
| 28 | + |
| 29 | +#include "lib/dshash.h" |
| 30 | +#include "storage/dsm_registry.h" |
| 31 | +#include "storage/lwlock.h" |
| 32 | +#include "storage/shmem.h" |
| 33 | +#include "utils/memutils.h" |
| 34 | + |
| 35 | +typedef struct DSMRegistryCtxStruct |
| 36 | +{ |
| 37 | + dsa_handle dsah; |
| 38 | + dshash_table_handle dshh; |
| 39 | +} DSMRegistryCtxStruct; |
| 40 | + |
| 41 | +static DSMRegistryCtxStruct *DSMRegistryCtx; |
| 42 | + |
| 43 | +typedef struct DSMRegistryEntry |
| 44 | +{ |
| 45 | + char name[64]; |
| 46 | + dsm_handle handle; |
| 47 | + size_t size; |
| 48 | +} DSMRegistryEntry; |
| 49 | + |
| 50 | +static const dshash_parameters dsh_params = { |
| 51 | + offsetof(DSMRegistryEntry, handle), |
| 52 | + sizeof(DSMRegistryEntry), |
| 53 | + dshash_memcmp, |
| 54 | + dshash_memhash, |
| 55 | + LWTRANCHE_DSM_REGISTRY_HASH |
| 56 | +}; |
| 57 | + |
| 58 | +static dsa_area *dsm_registry_dsa; |
| 59 | +static dshash_table *dsm_registry_table; |
| 60 | + |
| 61 | +Size |
| 62 | +DSMRegistryShmemSize(void) |
| 63 | +{ |
| 64 | + return MAXALIGN(sizeof(DSMRegistryCtxStruct)); |
| 65 | +} |
| 66 | + |
| 67 | +void |
| 68 | +DSMRegistryShmemInit(void) |
| 69 | +{ |
| 70 | + bool found; |
| 71 | + |
| 72 | + DSMRegistryCtx = (DSMRegistryCtxStruct *) |
| 73 | + ShmemInitStruct("DSM Registry Data", |
| 74 | + DSMRegistryShmemSize(), |
| 75 | + &found); |
| 76 | + |
| 77 | + if (!found) |
| 78 | + { |
| 79 | + DSMRegistryCtx->dsah = DSA_HANDLE_INVALID; |
| 80 | + DSMRegistryCtx->dshh = DSHASH_HANDLE_INVALID; |
| 81 | + } |
| 82 | +} |
| 83 | + |
| 84 | +/* |
| 85 | + * Initialize or attach to the dynamic shared hash table that stores the DSM |
| 86 | + * registry entries, if not already done. This must be called before accessing |
| 87 | + * the table. |
| 88 | + */ |
| 89 | +static void |
| 90 | +init_dsm_registry(void) |
| 91 | +{ |
| 92 | + /* Quick exit if we already did this. */ |
| 93 | + if (dsm_registry_table) |
| 94 | + return; |
| 95 | + |
| 96 | + /* Otherwise, use a lock to ensure only one process creates the table. */ |
| 97 | + LWLockAcquire(DSMRegistryLock, LW_EXCLUSIVE); |
| 98 | + |
| 99 | + if (DSMRegistryCtx->dshh == DSHASH_HANDLE_INVALID) |
| 100 | + { |
| 101 | + /* Initialize dynamic shared hash table for registry. */ |
| 102 | + dsm_registry_dsa = dsa_create(LWTRANCHE_DSM_REGISTRY_DSA); |
| 103 | + dsa_pin(dsm_registry_dsa); |
| 104 | + dsa_pin_mapping(dsm_registry_dsa); |
| 105 | + dsm_registry_table = dshash_create(dsm_registry_dsa, &dsh_params, NULL); |
| 106 | + |
| 107 | + /* Store handles in shared memory for other backends to use. */ |
| 108 | + DSMRegistryCtx->dsah = dsa_get_handle(dsm_registry_dsa); |
| 109 | + DSMRegistryCtx->dshh = dshash_get_hash_table_handle(dsm_registry_table); |
| 110 | + } |
| 111 | + else |
| 112 | + { |
| 113 | + /* Attach to existing dynamic shared hash table. */ |
| 114 | + dsm_registry_dsa = dsa_attach(DSMRegistryCtx->dsah); |
| 115 | + dsa_pin_mapping(dsm_registry_dsa); |
| 116 | + dsm_registry_table = dshash_attach(dsm_registry_dsa, &dsh_params, |
| 117 | + DSMRegistryCtx->dshh, NULL); |
| 118 | + } |
| 119 | + |
| 120 | + LWLockRelease(DSMRegistryLock); |
| 121 | +} |
| 122 | + |
| 123 | +/* |
| 124 | + * Initialize or attach a named DSM segment. |
| 125 | + * |
| 126 | + * This routine returns the address of the segment. init_callback is called to |
| 127 | + * initialize the segment when it is first created. |
| 128 | + */ |
| 129 | +void * |
| 130 | +GetNamedDSMSegment(const char *name, size_t size, |
| 131 | + void (*init_callback) (void *ptr), bool *found) |
| 132 | +{ |
| 133 | + DSMRegistryEntry *entry; |
| 134 | + MemoryContext oldcontext; |
| 135 | + char name_padded[offsetof(DSMRegistryEntry, handle)] = {0}; |
| 136 | + void *ret; |
| 137 | + |
| 138 | + Assert(found); |
| 139 | + |
| 140 | + if (!name || *name == '\0') |
| 141 | + ereport(ERROR, |
| 142 | + (errmsg("DSM segment name cannot be empty"))); |
| 143 | + |
| 144 | + if (strlen(name) >= offsetof(DSMRegistryEntry, handle)) |
| 145 | + ereport(ERROR, |
| 146 | + (errmsg("DSM segment name too long"))); |
| 147 | + |
| 148 | + if (size == 0) |
| 149 | + ereport(ERROR, |
| 150 | + (errmsg("DSM segment size must be nonzero"))); |
| 151 | + |
| 152 | + /* Be sure any local memory allocated by DSM/DSA routines is persistent. */ |
| 153 | + oldcontext = MemoryContextSwitchTo(TopMemoryContext); |
| 154 | + |
| 155 | + /* Connect to the registry. */ |
| 156 | + init_dsm_registry(); |
| 157 | + |
| 158 | + strcpy(name_padded, name); |
| 159 | + entry = dshash_find_or_insert(dsm_registry_table, name_padded, found); |
| 160 | + if (!(*found)) |
| 161 | + { |
| 162 | + /* Initialize the segment. */ |
| 163 | + dsm_segment *seg = dsm_create(size, 0); |
| 164 | + |
| 165 | + dsm_pin_segment(seg); |
| 166 | + dsm_pin_mapping(seg); |
| 167 | + entry->handle = dsm_segment_handle(seg); |
| 168 | + entry->size = size; |
| 169 | + ret = dsm_segment_address(seg); |
| 170 | + |
| 171 | + if (init_callback) |
| 172 | + (*init_callback) (ret); |
| 173 | + } |
| 174 | + else if (entry->size != size) |
| 175 | + { |
| 176 | + ereport(ERROR, |
| 177 | + (errmsg("requested DSM segment size does not match size of " |
| 178 | + "existing segment"))); |
| 179 | + } |
| 180 | + else if (!dsm_find_mapping(entry->handle)) |
| 181 | + { |
| 182 | + /* Attach to existing segment. */ |
| 183 | + dsm_segment *seg = dsm_attach(entry->handle); |
| 184 | + |
| 185 | + dsm_pin_mapping(seg); |
| 186 | + ret = dsm_segment_address(seg); |
| 187 | + } |
| 188 | + else |
| 189 | + { |
| 190 | + /* Return address of an already-attached segment. */ |
| 191 | + ret = dsm_segment_address(dsm_find_mapping(entry->handle)); |
| 192 | + } |
| 193 | + |
| 194 | + dshash_release_lock(dsm_registry_table, entry); |
| 195 | + MemoryContextSwitchTo(oldcontext); |
| 196 | + |
| 197 | + return ret; |
| 198 | +} |
0 commit comments