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 : : };
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
698 nathan@postgresql.or 117 :CBC 3055 : DSMRegistryShmemSize(void)
118 : : {
119 : 3055 : return MAXALIGN(sizeof(DSMRegistryCtxStruct));
120 : : }
121 : :
122 : : void
123 : 1069 : DSMRegistryShmemInit(void)
124 : : {
125 : : bool found;
126 : :
127 : 1069 : DSMRegistryCtx = (DSMRegistryCtxStruct *)
128 : 1069 : ShmemInitStruct("DSM Registry Data",
129 : : DSMRegistryShmemSize(),
130 : : &found);
131 : :
132 [ + - ]: 1069 : if (!found)
133 : : {
134 : 1069 : DSMRegistryCtx->dsah = DSA_HANDLE_INVALID;
135 : 1069 : DSMRegistryCtx->dshh = DSHASH_HANDLE_INVALID;
136 : : }
137 : 1069 : }
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 : 28 : init_dsm_registry(void)
146 : : {
147 : : /* Quick exit if we already did this. */
148 [ + + ]: 28 : if (dsm_registry_table)
698 nathan@postgresql.or 149 :GBC 8 : return;
150 : :
151 : : /* Otherwise, use a lock to ensure only one process creates the table. */
698 nathan@postgresql.or 152 :CBC 20 : LWLockAcquire(DSMRegistryLock, LW_EXCLUSIVE);
153 : :
154 [ + + ]: 20 : if (DSMRegistryCtx->dshh == DSHASH_HANDLE_INVALID)
155 : : {
156 : : /* Initialize dynamic shared hash table for registry. */
157 : 6 : dsm_registry_dsa = dsa_create(LWTRANCHE_DSM_REGISTRY_DSA);
21 158 : 6 : dsm_registry_table = dshash_create(dsm_registry_dsa, &dsh_params, NULL);
159 : :
698 160 : 6 : dsa_pin(dsm_registry_dsa);
161 : 6 : dsa_pin_mapping(dsm_registry_dsa);
162 : :
163 : : /* Store handles in shared memory for other backends to use. */
164 : 6 : DSMRegistryCtx->dsah = dsa_get_handle(dsm_registry_dsa);
165 : 6 : DSMRegistryCtx->dshh = dshash_get_hash_table_handle(dsm_registry_table);
166 : : }
167 : : else
168 : : {
169 : : /* Attach to existing dynamic shared hash table. */
170 : 14 : dsm_registry_dsa = dsa_attach(DSMRegistryCtx->dsah);
171 : 14 : dsa_pin_mapping(dsm_registry_dsa);
172 : 14 : dsm_registry_table = dshash_attach(dsm_registry_dsa, &dsh_params,
173 : 14 : DSMRegistryCtx->dshh, NULL);
174 : : }
175 : :
176 : 20 : 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 : 10 : 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 [ - + ]: 10 : Assert(found);
198 : :
199 [ + - - + ]: 10 : if (!name || *name == '\0')
698 nathan@postgresql.or 200 [ # # ]:UBC 0 : ereport(ERROR,
201 : : (errmsg("DSM segment name cannot be empty")));
202 : :
168 nathan@postgresql.or 203 [ - + ]:GNC 10 : if (strlen(name) >= offsetof(DSMRegistryEntry, type))
698 nathan@postgresql.or 204 [ # # ]:UBC 0 : ereport(ERROR,
205 : : (errmsg("DSM segment name too long")));
206 : :
698 nathan@postgresql.or 207 [ - + ]:CBC 10 : if (size == 0)
698 nathan@postgresql.or 208 [ # # ]:UBC 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. */
698 nathan@postgresql.or 212 :CBC 10 : oldcontext = MemoryContextSwitchTo(TopMemoryContext);
213 : :
214 : : /* Connect to the registry. */
215 : 10 : init_dsm_registry();
216 : :
660 217 : 10 : entry = dshash_find_or_insert(dsm_registry_table, name, found);
21 nathan@postgresql.or 218 :GNC 10 : state = &entry->dsm;
698 nathan@postgresql.or 219 [ + + ]:CBC 10 : if (!(*found))
220 : : {
168 nathan@postgresql.or 221 :GNC 4 : entry->type = DSMR_ENTRY_TYPE_DSM;
21 222 : 4 : state->handle = DSM_HANDLE_INVALID;
223 : 4 : state->size = size;
224 : : }
225 [ - + ]: 6 : else if (entry->type != DSMR_ENTRY_TYPE_DSM)
21 nathan@postgresql.or 226 [ # # ]:UNC 0 : ereport(ERROR,
227 : : (errmsg("requested DSM segment does not match type of existing entry")));
21 nathan@postgresql.or 228 [ - + ]:GNC 6 : else if (state->size != size)
21 nathan@postgresql.or 229 [ # # ]:UBC 0 : ereport(ERROR,
230 : : (errmsg("requested DSM segment size does not match size of existing segment")));
231 : :
21 nathan@postgresql.or 232 [ + + ]:GNC 10 : if (state->handle == DSM_HANDLE_INVALID)
233 : : {
21 nathan@postgresql.or 234 :CBC 4 : *found = false;
235 : :
236 : : /* Initialize the segment. */
168 237 : 4 : seg = dsm_create(size, 0);
238 : :
21 239 [ + - ]: 4 : if (init_callback)
2 nathan@postgresql.or 240 :GNC 4 : (*init_callback) (dsm_segment_address(seg), arg);
241 : :
698 nathan@postgresql.or 242 :CBC 4 : dsm_pin_segment(seg);
243 : 4 : dsm_pin_mapping(seg);
168 nathan@postgresql.or 244 :GNC 4 : state->handle = dsm_segment_handle(seg);
245 : : }
246 : : else
247 : : {
248 : : /* If the existing segment is not already attached, attach it now. */
249 : 6 : seg = dsm_find_mapping(state->handle);
695 nathan@postgresql.or 250 [ + + ]:CBC 6 : if (seg == NULL)
251 : : {
168 nathan@postgresql.or 252 :GNC 3 : seg = dsm_attach(state->handle);
695 nathan@postgresql.or 253 [ - + ]:CBC 3 : if (seg == NULL)
695 nathan@postgresql.or 254 [ # # ]:UBC 0 : elog(ERROR, "could not map dynamic shared memory segment");
255 : :
695 nathan@postgresql.or 256 :CBC 3 : dsm_pin_mapping(seg);
257 : : }
258 : : }
259 : :
21 260 : 10 : ret = dsm_segment_address(seg);
698 261 : 10 : dshash_release_lock(dsm_registry_table, entry);
262 : 10 : MemoryContextSwitchTo(oldcontext);
263 : :
264 : 10 : 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 *
168 nathan@postgresql.or 276 :GNC 14 : GetNamedDSA(const char *name, bool *found)
277 : : {
278 : : DSMRegistryEntry *entry;
279 : : MemoryContext oldcontext;
280 : : dsa_area *ret;
281 : : NamedDSAState *state;
282 : :
283 [ - + ]: 14 : Assert(found);
284 : :
285 [ + - - + ]: 14 : if (!name || *name == '\0')
168 nathan@postgresql.or 286 [ # # ]:UNC 0 : ereport(ERROR,
287 : : (errmsg("DSA name cannot be empty")));
288 : :
168 nathan@postgresql.or 289 [ - + ]:GNC 14 : if (strlen(name) >= offsetof(DSMRegistryEntry, type))
168 nathan@postgresql.or 290 [ # # ]:UNC 0 : ereport(ERROR,
291 : : (errmsg("DSA name too long")));
292 : :
293 : : /* Be sure any local memory allocated by DSM/DSA routines is persistent. */
168 nathan@postgresql.or 294 :GNC 14 : oldcontext = MemoryContextSwitchTo(TopMemoryContext);
295 : :
296 : : /* Connect to the registry. */
297 : 14 : init_dsm_registry();
298 : :
299 : 14 : entry = dshash_find_or_insert(dsm_registry_table, name, found);
21 300 : 14 : state = &entry->dsa;
168 301 [ + + ]: 14 : if (!(*found))
302 : : {
303 : 3 : entry->type = DSMR_ENTRY_TYPE_DSA;
21 304 : 3 : state->handle = DSA_HANDLE_INVALID;
305 : 3 : state->tranche = -1;
306 : : }
307 [ - + ]: 11 : else if (entry->type != DSMR_ENTRY_TYPE_DSA)
21 nathan@postgresql.or 308 [ # # ]:UNC 0 : ereport(ERROR,
309 : : (errmsg("requested DSA does not match type of existing entry")));
310 : :
21 nathan@postgresql.or 311 [ + + ]:GNC 14 : if (state->tranche == -1)
312 : : {
313 : 3 : *found = false;
314 : :
315 : : /* Initialize the LWLock tranche for the DSA. */
105 316 : 3 : state->tranche = LWLockNewTrancheId(name);
317 : : }
318 : :
21 319 [ + + ]: 14 : if (state->handle == DSA_HANDLE_INVALID)
320 : : {
321 : 3 : *found = false;
322 : :
323 : : /* Initialize the DSA. */
168 324 : 3 : ret = dsa_create(state->tranche);
325 : 3 : dsa_pin(ret);
326 : 3 : dsa_pin_mapping(ret);
327 : :
328 : : /* Store handle for other backends to use. */
329 : 3 : state->handle = dsa_get_handle(ret);
330 : : }
21 331 [ - + ]: 11 : else if (dsa_is_attached(state->handle))
168 nathan@postgresql.or 332 [ # # ]:UNC 0 : ereport(ERROR,
333 : : (errmsg("requested DSA already attached to current process")));
334 : : else
335 : : {
336 : : /* Attach to existing DSA. */
168 nathan@postgresql.or 337 :GNC 11 : ret = dsa_attach(state->handle);
338 : 11 : dsa_pin_mapping(ret);
339 : : }
340 : :
341 : 14 : dshash_release_lock(dsm_registry_table, entry);
342 : 14 : MemoryContextSwitchTo(oldcontext);
343 : :
344 : 14 : 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 : 2 : 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 [ - + ]: 2 : Assert(params);
365 [ - + ]: 2 : Assert(found);
366 : :
367 [ + - - + ]: 2 : if (!name || *name == '\0')
168 nathan@postgresql.or 368 [ # # ]:UNC 0 : ereport(ERROR,
369 : : (errmsg("DSHash name cannot be empty")));
370 : :
168 nathan@postgresql.or 371 [ - + ]:GNC 2 : if (strlen(name) >= offsetof(DSMRegistryEntry, type))
168 nathan@postgresql.or 372 [ # # ]:UNC 0 : ereport(ERROR,
373 : : (errmsg("DSHash name too long")));
374 : :
375 : : /* Be sure any local memory allocated by DSM/DSA routines is persistent. */
168 nathan@postgresql.or 376 :GNC 2 : oldcontext = MemoryContextSwitchTo(TopMemoryContext);
377 : :
378 : : /* Connect to the registry. */
379 : 2 : init_dsm_registry();
380 : :
381 : 2 : entry = dshash_find_or_insert(dsm_registry_table, name, found);
21 382 : 2 : dsh_state = &entry->dsh;
168 383 [ + + ]: 2 : if (!(*found))
384 : : {
385 : 1 : entry->type = DSMR_ENTRY_TYPE_DSH;
21 386 : 1 : dsh_state->dsa_handle = DSA_HANDLE_INVALID;
387 : 1 : dsh_state->dsh_handle = DSHASH_HANDLE_INVALID;
388 : 1 : dsh_state->tranche = -1;
389 : : }
390 [ - + ]: 1 : else if (entry->type != DSMR_ENTRY_TYPE_DSH)
21 nathan@postgresql.or 391 [ # # ]:UNC 0 : ereport(ERROR,
392 : : (errmsg("requested DSHash does not match type of existing entry")));
393 : :
21 nathan@postgresql.or 394 [ + + ]:GNC 2 : if (dsh_state->tranche == -1)
395 : : {
396 : 1 : *found = false;
397 : :
398 : : /* Initialize the LWLock tranche for the hash table. */
105 399 : 1 : dsh_state->tranche = LWLockNewTrancheId(name);
400 : : }
401 : :
21 402 [ + + ]: 2 : if (dsh_state->dsa_handle == DSA_HANDLE_INVALID)
403 : : {
404 : : dshash_parameters params_copy;
405 : : dsa_area *dsa;
406 : :
407 : 1 : *found = false;
408 : :
409 : : /* Initialize the DSA for the hash table. */
110 410 : 1 : dsa = dsa_create(dsh_state->tranche);
411 : :
412 : : /* Initialize the dshash table. */
168 413 : 1 : memcpy(¶ms_copy, params, sizeof(dshash_parameters));
414 : 1 : params_copy.tranche_id = dsh_state->tranche;
415 : 1 : ret = dshash_create(dsa, ¶ms_copy, NULL);
416 : :
21 417 : 1 : dsa_pin(dsa);
418 : 1 : dsa_pin_mapping(dsa);
419 : :
420 : : /* Store handles for other backends to use. */
110 421 : 1 : dsh_state->dsa_handle = dsa_get_handle(dsa);
422 : 1 : dsh_state->dsh_handle = dshash_get_hash_table_handle(ret);
423 : : }
21 424 [ - + ]: 1 : else if (dsa_is_attached(dsh_state->dsa_handle))
168 nathan@postgresql.or 425 [ # # ]:UNC 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. */
110 nathan@postgresql.or 434 :GNC 1 : dsa = dsa_attach(dsh_state->dsa_handle);
168 435 : 1 : dsa_pin_mapping(dsa);
436 : :
437 : : /* Attach to existing dshash table. */
110 438 : 1 : ret = dshash_attach(dsa, params, dsh_state->dsh_handle, NULL);
439 : : }
440 : :
168 441 : 2 : dshash_release_lock(dsm_registry_table, entry);
442 : 2 : MemoryContextSwitchTo(oldcontext);
443 : :
444 : 2 : return ret;
445 : : }
446 : :
447 : : Datum
161 448 : 2 : pg_get_dsm_registry_allocations(PG_FUNCTION_ARGS)
449 : : {
450 : 2 : ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
451 : : DSMRegistryEntry *entry;
452 : : MemoryContext oldcontext;
453 : : dshash_seq_status status;
454 : :
455 : 2 : InitMaterializedSRF(fcinfo, MAT_SRF_USE_EXPECTED_DESC);
456 : :
457 : : /* Be sure any local memory allocated by DSM/DSA routines is persistent. */
458 : 2 : oldcontext = MemoryContextSwitchTo(TopMemoryContext);
459 : 2 : init_dsm_registry();
460 : 2 : MemoryContextSwitchTo(oldcontext);
461 : :
462 : 2 : dshash_seq_init(&status, dsm_registry_table, false);
463 [ + + ]: 5 : while ((entry = dshash_seq_next(&status)) != NULL)
464 : : {
465 : : Datum vals[3];
466 : 3 : bool nulls[3] = {0};
467 : :
468 : 3 : vals[0] = CStringGetTextDatum(entry->name);
469 : 3 : vals[1] = CStringGetTextDatum(DSMREntryTypeNames[entry->type]);
470 : :
471 : : /* Be careful to only return the sizes of initialized entries. */
15 472 [ + + ]: 3 : if (entry->type == DSMR_ENTRY_TYPE_DSM &&
473 [ + - ]: 1 : entry->dsm.handle != DSM_HANDLE_INVALID)
75 474 : 1 : vals[2] = Int64GetDatum(entry->dsm.size);
15 475 [ + + ]: 2 : else if (entry->type == DSMR_ENTRY_TYPE_DSA &&
476 [ + - ]: 1 : entry->dsa.handle != DSA_HANDLE_INVALID)
477 : 1 : vals[2] = Int64GetDatum(dsa_get_total_size_from_handle(entry->dsa.handle));
478 [ + - ]: 1 : else if (entry->type == DSMR_ENTRY_TYPE_DSH &&
479 [ + - ]: 1 : entry->dsh.dsa_handle !=DSA_HANDLE_INVALID)
480 : 1 : vals[2] = Int64GetDatum(dsa_get_total_size_from_handle(entry->dsh.dsa_handle));
481 : : else
161 nathan@postgresql.or 482 :UNC 0 : nulls[2] = true;
483 : :
161 nathan@postgresql.or 484 :GNC 3 : tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, vals, nulls);
485 : : }
486 : 2 : dshash_seq_term(&status);
487 : :
488 : 2 : return (Datum) 0;
489 : : }
|