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