Age Owner Branch data TLA 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 : : } data;
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
596 nathan@postgresql.or 117 :CBC 2938 : DSMRegistryShmemSize(void)
118 : : {
119 : 2938 : return MAXALIGN(sizeof(DSMRegistryCtxStruct));
120 : : }
121 : :
122 : : void
123 : 1029 : DSMRegistryShmemInit(void)
124 : : {
125 : : bool found;
126 : :
127 : 1029 : DSMRegistryCtx = (DSMRegistryCtxStruct *)
128 : 1029 : ShmemInitStruct("DSM Registry Data",
129 : : DSMRegistryShmemSize(),
130 : : &found);
131 : :
132 [ + - ]: 1029 : if (!found)
133 : : {
134 : 1029 : DSMRegistryCtx->dsah = DSA_HANDLE_INVALID;
135 : 1029 : DSMRegistryCtx->dshh = DSHASH_HANDLE_INVALID;
136 : : }
137 : 1029 : }
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 : 14 : init_dsm_registry(void)
146 : : {
147 : : /* Quick exit if we already did this. */
148 [ + + ]: 14 : if (dsm_registry_table)
596 nathan@postgresql.or 149 :GBC 7 : return;
150 : :
151 : : /* Otherwise, use a lock to ensure only one process creates the table. */
596 nathan@postgresql.or 152 :CBC 7 : LWLockAcquire(DSMRegistryLock, LW_EXCLUSIVE);
153 : :
154 [ + + ]: 7 : if (DSMRegistryCtx->dshh == DSHASH_HANDLE_INVALID)
155 : : {
156 : : /* Initialize dynamic shared hash table for registry. */
157 : 3 : dsm_registry_dsa = dsa_create(LWTRANCHE_DSM_REGISTRY_DSA);
158 : 3 : dsa_pin(dsm_registry_dsa);
159 : 3 : dsa_pin_mapping(dsm_registry_dsa);
160 : 3 : dsm_registry_table = dshash_create(dsm_registry_dsa, &dsh_params, NULL);
161 : :
162 : : /* Store handles in shared memory for other backends to use. */
163 : 3 : DSMRegistryCtx->dsah = dsa_get_handle(dsm_registry_dsa);
164 : 3 : DSMRegistryCtx->dshh = dshash_get_hash_table_handle(dsm_registry_table);
165 : : }
166 : : else
167 : : {
168 : : /* Attach to existing dynamic shared hash table. */
169 : 4 : dsm_registry_dsa = dsa_attach(DSMRegistryCtx->dsah);
170 : 4 : dsa_pin_mapping(dsm_registry_dsa);
171 : 4 : dsm_registry_table = dshash_attach(dsm_registry_dsa, &dsh_params,
172 : 4 : DSMRegistryCtx->dshh, NULL);
173 : : }
174 : :
175 : 7 : LWLockRelease(DSMRegistryLock);
176 : : }
177 : :
178 : : /*
179 : : * Initialize or attach a named DSM segment.
180 : : *
181 : : * This routine returns the address of the segment. init_callback is called to
182 : : * initialize the segment when it is first created.
183 : : */
184 : : void *
185 : 8 : GetNamedDSMSegment(const char *name, size_t size,
186 : : void (*init_callback) (void *ptr), bool *found)
187 : : {
188 : : DSMRegistryEntry *entry;
189 : : MemoryContext oldcontext;
190 : : void *ret;
191 : :
192 [ - + ]: 8 : Assert(found);
193 : :
194 [ + - - + ]: 8 : if (!name || *name == '\0')
596 nathan@postgresql.or 195 [ # # ]:UBC 0 : ereport(ERROR,
196 : : (errmsg("DSM segment name cannot be empty")));
197 : :
66 nathan@postgresql.or 198 [ - + ]:GNC 8 : if (strlen(name) >= offsetof(DSMRegistryEntry, type))
596 nathan@postgresql.or 199 [ # # ]:UBC 0 : ereport(ERROR,
200 : : (errmsg("DSM segment name too long")));
201 : :
596 nathan@postgresql.or 202 [ - + ]:CBC 8 : if (size == 0)
596 nathan@postgresql.or 203 [ # # ]:UBC 0 : ereport(ERROR,
204 : : (errmsg("DSM segment size must be nonzero")));
205 : :
206 : : /* Be sure any local memory allocated by DSM/DSA routines is persistent. */
596 nathan@postgresql.or 207 :CBC 8 : oldcontext = MemoryContextSwitchTo(TopMemoryContext);
208 : :
209 : : /* Connect to the registry. */
210 : 8 : init_dsm_registry();
211 : :
558 212 : 8 : entry = dshash_find_or_insert(dsm_registry_table, name, found);
596 213 [ + + ]: 8 : if (!(*found))
214 : : {
66 nathan@postgresql.or 215 :GNC 3 : NamedDSMState *state = &entry->data.dsm;
216 : : dsm_segment *seg;
217 : :
218 : 3 : entry->type = DSMR_ENTRY_TYPE_DSM;
219 : :
220 : : /* Initialize the segment. */
221 : 3 : seg = dsm_create(size, 0);
222 : :
596 nathan@postgresql.or 223 :CBC 3 : dsm_pin_segment(seg);
224 : 3 : dsm_pin_mapping(seg);
66 nathan@postgresql.or 225 :GNC 3 : state->handle = dsm_segment_handle(seg);
226 : 3 : state->size = size;
596 nathan@postgresql.or 227 :CBC 3 : ret = dsm_segment_address(seg);
228 : :
229 [ + - ]: 3 : if (init_callback)
230 : 3 : (*init_callback) (ret);
231 : : }
66 nathan@postgresql.or 232 [ - + ]:GNC 5 : else if (entry->type != DSMR_ENTRY_TYPE_DSM)
596 nathan@postgresql.or 233 [ # # ]:UBC 0 : ereport(ERROR,
234 : : (errmsg("requested DSM segment does not match type of existing entry")));
66 nathan@postgresql.or 235 [ - + ]:GNC 5 : else if (entry->data.dsm.size != size)
66 nathan@postgresql.or 236 [ # # ]:UNC 0 : ereport(ERROR,
237 : : (errmsg("requested DSM segment size does not match size of existing segment")));
238 : : else
239 : : {
66 nathan@postgresql.or 240 :GNC 5 : NamedDSMState *state = &entry->data.dsm;
241 : : dsm_segment *seg;
242 : :
243 : : /* If the existing segment is not already attached, attach it now. */
244 : 5 : seg = dsm_find_mapping(state->handle);
593 nathan@postgresql.or 245 [ + + ]:CBC 5 : if (seg == NULL)
246 : : {
66 nathan@postgresql.or 247 :GNC 3 : seg = dsm_attach(state->handle);
593 nathan@postgresql.or 248 [ - + ]:CBC 3 : if (seg == NULL)
593 nathan@postgresql.or 249 [ # # ]:UBC 0 : elog(ERROR, "could not map dynamic shared memory segment");
250 : :
593 nathan@postgresql.or 251 :CBC 3 : dsm_pin_mapping(seg);
252 : : }
253 : :
596 254 : 5 : ret = dsm_segment_address(seg);
255 : : }
256 : :
257 : 8 : dshash_release_lock(dsm_registry_table, entry);
258 : 8 : MemoryContextSwitchTo(oldcontext);
259 : :
260 : 8 : return ret;
261 : : }
262 : :
263 : : /*
264 : : * Initialize or attach a named DSA.
265 : : *
266 : : * This routine returns a pointer to the DSA. A new LWLock tranche ID will be
267 : : * generated if needed. Note that the lock tranche will be registered with the
268 : : * provided name. Also note that this should be called at most once for a
269 : : * given DSA in each backend.
270 : : */
271 : : dsa_area *
66 nathan@postgresql.or 272 :GNC 2 : GetNamedDSA(const char *name, bool *found)
273 : : {
274 : : DSMRegistryEntry *entry;
275 : : MemoryContext oldcontext;
276 : : dsa_area *ret;
277 : :
278 [ - + ]: 2 : Assert(found);
279 : :
280 [ + - - + ]: 2 : if (!name || *name == '\0')
66 nathan@postgresql.or 281 [ # # ]:UNC 0 : ereport(ERROR,
282 : : (errmsg("DSA name cannot be empty")));
283 : :
66 nathan@postgresql.or 284 [ - + ]:GNC 2 : if (strlen(name) >= offsetof(DSMRegistryEntry, type))
66 nathan@postgresql.or 285 [ # # ]:UNC 0 : ereport(ERROR,
286 : : (errmsg("DSA name too long")));
287 : :
288 : : /* Be sure any local memory allocated by DSM/DSA routines is persistent. */
66 nathan@postgresql.or 289 :GNC 2 : oldcontext = MemoryContextSwitchTo(TopMemoryContext);
290 : :
291 : : /* Connect to the registry. */
292 : 2 : init_dsm_registry();
293 : :
294 : 2 : entry = dshash_find_or_insert(dsm_registry_table, name, found);
295 [ + + ]: 2 : if (!(*found))
296 : : {
297 : 1 : NamedDSAState *state = &entry->data.dsa;
298 : :
299 : 1 : entry->type = DSMR_ENTRY_TYPE_DSA;
300 : :
301 : : /* Initialize the LWLock tranche for the DSA. */
3 302 : 1 : state->tranche = LWLockNewTrancheId(name);
303 : :
304 : : /* Initialize the DSA. */
66 305 : 1 : ret = dsa_create(state->tranche);
306 : 1 : dsa_pin(ret);
307 : 1 : dsa_pin_mapping(ret);
308 : :
309 : : /* Store handle for other backends to use. */
310 : 1 : state->handle = dsa_get_handle(ret);
311 : : }
312 [ - + ]: 1 : else if (entry->type != DSMR_ENTRY_TYPE_DSA)
66 nathan@postgresql.or 313 [ # # ]:UNC 0 : ereport(ERROR,
314 : : (errmsg("requested DSA does not match type of existing entry")));
315 : : else
316 : : {
66 nathan@postgresql.or 317 :GNC 1 : NamedDSAState *state = &entry->data.dsa;
318 : :
319 [ - + ]: 1 : if (dsa_is_attached(state->handle))
66 nathan@postgresql.or 320 [ # # ]:UNC 0 : ereport(ERROR,
321 : : (errmsg("requested DSA already attached to current process")));
322 : :
323 : : /* Attach to existing DSA. */
66 nathan@postgresql.or 324 :GNC 1 : ret = dsa_attach(state->handle);
325 : 1 : dsa_pin_mapping(ret);
326 : : }
327 : :
328 : 2 : dshash_release_lock(dsm_registry_table, entry);
329 : 2 : MemoryContextSwitchTo(oldcontext);
330 : :
331 : 2 : return ret;
332 : : }
333 : :
334 : : /*
335 : : * Initialize or attach a named dshash table.
336 : : *
337 : : * This routine returns the address of the table. The tranche_id member of
338 : : * params is ignored; a new LWLock tranche ID will be generated if needed.
339 : : * Note that the lock tranche will be registered with the provided name. Also
340 : : * note that this should be called at most once for a given table in each
341 : : * backend.
342 : : */
343 : : dshash_table *
344 : 2 : GetNamedDSHash(const char *name, const dshash_parameters *params, bool *found)
345 : : {
346 : : DSMRegistryEntry *entry;
347 : : MemoryContext oldcontext;
348 : : dshash_table *ret;
349 : :
350 [ - + ]: 2 : Assert(params);
351 [ - + ]: 2 : Assert(found);
352 : :
353 [ + - - + ]: 2 : if (!name || *name == '\0')
66 nathan@postgresql.or 354 [ # # ]:UNC 0 : ereport(ERROR,
355 : : (errmsg("DSHash name cannot be empty")));
356 : :
66 nathan@postgresql.or 357 [ - + ]:GNC 2 : if (strlen(name) >= offsetof(DSMRegistryEntry, type))
66 nathan@postgresql.or 358 [ # # ]:UNC 0 : ereport(ERROR,
359 : : (errmsg("DSHash name too long")));
360 : :
361 : : /* Be sure any local memory allocated by DSM/DSA routines is persistent. */
66 nathan@postgresql.or 362 :GNC 2 : oldcontext = MemoryContextSwitchTo(TopMemoryContext);
363 : :
364 : : /* Connect to the registry. */
365 : 2 : init_dsm_registry();
366 : :
367 : 2 : entry = dshash_find_or_insert(dsm_registry_table, name, found);
368 [ + + ]: 2 : if (!(*found))
369 : : {
370 : 1 : NamedDSHState *dsh_state = &entry->data.dsh;
371 : : dshash_parameters params_copy;
372 : : dsa_area *dsa;
373 : :
374 : 1 : entry->type = DSMR_ENTRY_TYPE_DSH;
375 : :
376 : : /* Initialize the LWLock tranche for the hash table. */
3 377 : 1 : dsh_state->tranche = LWLockNewTrancheId(name);
378 : :
379 : : /* Initialize the DSA for the hash table. */
8 380 : 1 : dsa = dsa_create(dsh_state->tranche);
66 381 : 1 : dsa_pin(dsa);
382 : 1 : dsa_pin_mapping(dsa);
383 : :
384 : : /* Initialize the dshash table. */
385 : 1 : memcpy(¶ms_copy, params, sizeof(dshash_parameters));
386 : 1 : params_copy.tranche_id = dsh_state->tranche;
387 : 1 : ret = dshash_create(dsa, ¶ms_copy, NULL);
388 : :
389 : : /* Store handles for other backends to use. */
8 390 : 1 : dsh_state->dsa_handle = dsa_get_handle(dsa);
391 : 1 : dsh_state->dsh_handle = dshash_get_hash_table_handle(ret);
392 : : }
66 393 [ - + ]: 1 : else if (entry->type != DSMR_ENTRY_TYPE_DSH)
66 nathan@postgresql.or 394 [ # # ]:UNC 0 : ereport(ERROR,
395 : : (errmsg("requested DSHash does not match type of existing entry")));
396 : : else
397 : : {
66 nathan@postgresql.or 398 :GNC 1 : NamedDSHState *dsh_state = &entry->data.dsh;
399 : : dsa_area *dsa;
400 : :
401 : : /* XXX: Should we verify params matches what table was created with? */
402 : :
8 403 [ - + ]: 1 : if (dsa_is_attached(dsh_state->dsa_handle))
66 nathan@postgresql.or 404 [ # # ]:UNC 0 : ereport(ERROR,
405 : : (errmsg("requested DSHash already attached to current process")));
406 : :
407 : : /* Attach to existing DSA for the hash table. */
8 nathan@postgresql.or 408 :GNC 1 : dsa = dsa_attach(dsh_state->dsa_handle);
66 409 : 1 : dsa_pin_mapping(dsa);
410 : :
411 : : /* Attach to existing dshash table. */
8 412 : 1 : ret = dshash_attach(dsa, params, dsh_state->dsh_handle, NULL);
413 : : }
414 : :
66 415 : 2 : dshash_release_lock(dsm_registry_table, entry);
416 : 2 : MemoryContextSwitchTo(oldcontext);
417 : :
418 : 2 : return ret;
419 : : }
420 : :
421 : : Datum
59 422 : 2 : pg_get_dsm_registry_allocations(PG_FUNCTION_ARGS)
423 : : {
424 : 2 : ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
425 : : DSMRegistryEntry *entry;
426 : : MemoryContext oldcontext;
427 : : dshash_seq_status status;
428 : :
429 : 2 : InitMaterializedSRF(fcinfo, MAT_SRF_USE_EXPECTED_DESC);
430 : :
431 : : /* Be sure any local memory allocated by DSM/DSA routines is persistent. */
432 : 2 : oldcontext = MemoryContextSwitchTo(TopMemoryContext);
433 : 2 : init_dsm_registry();
434 : 2 : MemoryContextSwitchTo(oldcontext);
435 : :
436 : 2 : dshash_seq_init(&status, dsm_registry_table, false);
437 [ + + ]: 5 : while ((entry = dshash_seq_next(&status)) != NULL)
438 : : {
439 : : Datum vals[3];
440 : 3 : bool nulls[3] = {0};
441 : :
442 : 3 : vals[0] = CStringGetTextDatum(entry->name);
443 : 3 : vals[1] = CStringGetTextDatum(DSMREntryTypeNames[entry->type]);
444 : :
445 : : /*
446 : : * Since we can't know the size of DSA/dshash entries without first
447 : : * attaching to them, return NULL for those.
448 : : */
449 [ + + ]: 3 : if (entry->type == DSMR_ENTRY_TYPE_DSM)
450 : 1 : vals[2] = Int64GetDatum(entry->data.dsm.size);
451 : : else
452 : 2 : nulls[2] = true;
453 : :
454 : 3 : tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, vals, nulls);
455 : : }
456 : 2 : dshash_seq_term(&status);
457 : :
458 : 2 : return (Datum) 0;
459 : : }
|