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