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