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
127 1238 : DSMRegistryShmemRequest(void *arg)
128 : {
129 1238 : ShmemRequestStruct(.name = "DSM Registry Data",
130 : .size = sizeof(DSMRegistryCtxStruct),
131 : .ptr = (void **) &DSMRegistryCtx,
132 : );
133 1238 : }
134 :
135 : static void
136 1235 : DSMRegistryShmemInit(void *arg)
137 : {
138 1235 : DSMRegistryCtx->dsah = DSA_HANDLE_INVALID;
139 1235 : DSMRegistryCtx->dshh = DSHASH_HANDLE_INVALID;
140 1235 : }
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 172 : init_dsm_registry(void)
149 : {
150 : /* Quick exit if we already did this. */
151 172 : if (dsm_registry_table)
152 10 : return;
153 :
154 : /* Otherwise, use a lock to ensure only one process creates the table. */
155 162 : LWLockAcquire(DSMRegistryLock, LW_EXCLUSIVE);
156 :
157 162 : if (DSMRegistryCtx->dshh == DSHASH_HANDLE_INVALID)
158 : {
159 : /* Initialize dynamic shared hash table for registry. */
160 24 : dsm_registry_dsa = dsa_create(LWTRANCHE_DSM_REGISTRY_DSA);
161 24 : dsm_registry_table = dshash_create(dsm_registry_dsa, &dsh_params, NULL);
162 :
163 24 : dsa_pin(dsm_registry_dsa);
164 24 : dsa_pin_mapping(dsm_registry_dsa);
165 :
166 : /* Store handles in shared memory for other backends to use. */
167 24 : DSMRegistryCtx->dsah = dsa_get_handle(dsm_registry_dsa);
168 24 : DSMRegistryCtx->dshh = dshash_get_hash_table_handle(dsm_registry_table);
169 : }
170 : else
171 : {
172 : /* Attach to existing dynamic shared hash table. */
173 138 : dsm_registry_dsa = dsa_attach(DSMRegistryCtx->dsah);
174 138 : dsa_pin_mapping(dsm_registry_dsa);
175 138 : dsm_registry_table = dshash_attach(dsm_registry_dsa, &dsh_params,
176 138 : DSMRegistryCtx->dshh, NULL);
177 : }
178 :
179 162 : 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 154 : 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 : Assert(found);
201 :
202 154 : if (!name || *name == '\0')
203 0 : ereport(ERROR,
204 : (errmsg("DSM segment name cannot be empty")));
205 :
206 154 : if (strlen(name) >= offsetof(DSMRegistryEntry, type))
207 0 : ereport(ERROR,
208 : (errmsg("DSM segment name too long")));
209 :
210 154 : 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 154 : oldcontext = MemoryContextSwitchTo(TopMemoryContext);
216 :
217 : /* Connect to the registry. */
218 154 : init_dsm_registry();
219 :
220 154 : entry = dshash_find_or_insert(dsm_registry_table, name, found);
221 154 : state = &entry->dsm;
222 154 : if (!(*found))
223 : {
224 22 : entry->type = DSMR_ENTRY_TYPE_DSM;
225 22 : state->handle = DSM_HANDLE_INVALID;
226 22 : state->size = size;
227 : }
228 132 : else if (entry->type != DSMR_ENTRY_TYPE_DSM)
229 0 : ereport(ERROR,
230 : (errmsg("requested DSM segment does not match type of existing entry")));
231 132 : else if (state->size != size)
232 0 : ereport(ERROR,
233 : (errmsg("requested DSM segment size does not match size of existing segment")));
234 :
235 154 : if (state->handle == DSM_HANDLE_INVALID)
236 : {
237 22 : *found = false;
238 :
239 : /* Initialize the segment. */
240 22 : seg = dsm_create(size, 0);
241 :
242 22 : if (init_callback)
243 22 : (*init_callback) (dsm_segment_address(seg), arg);
244 :
245 22 : dsm_pin_segment(seg);
246 22 : dsm_pin_mapping(seg);
247 22 : state->handle = dsm_segment_handle(seg);
248 : }
249 : else
250 : {
251 : /* If the existing segment is not already attached, attach it now. */
252 132 : seg = dsm_find_mapping(state->handle);
253 132 : if (seg == NULL)
254 : {
255 127 : seg = dsm_attach(state->handle);
256 127 : if (seg == NULL)
257 0 : elog(ERROR, "could not map dynamic shared memory segment");
258 :
259 127 : dsm_pin_mapping(seg);
260 : }
261 : }
262 :
263 154 : ret = dsm_segment_address(seg);
264 154 : dshash_release_lock(dsm_registry_table, entry);
265 154 : MemoryContextSwitchTo(oldcontext);
266 :
267 154 : 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 *
279 14 : GetNamedDSA(const char *name, bool *found)
280 : {
281 : DSMRegistryEntry *entry;
282 : MemoryContext oldcontext;
283 : dsa_area *ret;
284 : NamedDSAState *state;
285 :
286 : Assert(found);
287 :
288 14 : if (!name || *name == '\0')
289 0 : ereport(ERROR,
290 : (errmsg("DSA name cannot be empty")));
291 :
292 14 : 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 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);
303 14 : state = &entry->dsa;
304 14 : if (!(*found))
305 : {
306 3 : entry->type = DSMR_ENTRY_TYPE_DSA;
307 3 : state->handle = DSA_HANDLE_INVALID;
308 3 : state->tranche = -1;
309 : }
310 11 : else if (entry->type != DSMR_ENTRY_TYPE_DSA)
311 0 : ereport(ERROR,
312 : (errmsg("requested DSA does not match type of existing entry")));
313 :
314 14 : if (state->tranche == -1)
315 : {
316 3 : *found = false;
317 :
318 : /* Initialize the LWLock tranche for the DSA. */
319 3 : state->tranche = LWLockNewTrancheId(name);
320 : }
321 :
322 14 : if (state->handle == DSA_HANDLE_INVALID)
323 : {
324 3 : *found = false;
325 :
326 : /* Initialize the DSA. */
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 : }
334 11 : else if (dsa_is_attached(state->handle))
335 0 : ereport(ERROR,
336 : (errmsg("requested DSA already attached to current process")));
337 : else
338 : {
339 : /* Attach to existing DSA. */
340 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 : Assert(params);
368 : Assert(found);
369 :
370 2 : if (!name || *name == '\0')
371 0 : ereport(ERROR,
372 : (errmsg("DSHash name cannot be empty")));
373 :
374 2 : if (strlen(name) >= offsetof(DSMRegistryEntry, type))
375 0 : ereport(ERROR,
376 : (errmsg("DSHash name too long")));
377 :
378 : /* Be sure any local memory allocated by DSM/DSA routines is persistent. */
379 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);
385 2 : dsh_state = &entry->dsh;
386 2 : if (!(*found))
387 : {
388 1 : entry->type = DSMR_ENTRY_TYPE_DSH;
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)
394 0 : ereport(ERROR,
395 : (errmsg("requested DSHash does not match type of existing entry")));
396 :
397 2 : if (dsh_state->tranche == -1)
398 : {
399 1 : *found = false;
400 :
401 : /* Initialize the LWLock tranche for the hash table. */
402 1 : dsh_state->tranche = LWLockNewTrancheId(name);
403 : }
404 :
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. */
413 1 : dsa = dsa_create(dsh_state->tranche);
414 :
415 : /* Initialize the dshash table. */
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 :
420 1 : dsa_pin(dsa);
421 1 : dsa_pin_mapping(dsa);
422 :
423 : /* Store handles for other backends to use. */
424 1 : dsh_state->dsa_handle = dsa_get_handle(dsa);
425 1 : dsh_state->dsh_handle = dshash_get_hash_table_handle(ret);
426 : }
427 1 : else if (dsa_is_attached(dsh_state->dsa_handle))
428 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. */
437 1 : dsa = dsa_attach(dsh_state->dsa_handle);
438 1 : dsa_pin_mapping(dsa);
439 :
440 : /* Attach to existing dshash table. */
441 1 : ret = dshash_attach(dsa, params, dsh_state->dsh_handle, NULL);
442 : }
443 :
444 2 : dshash_release_lock(dsm_registry_table, entry);
445 2 : MemoryContextSwitchTo(oldcontext);
446 :
447 2 : return ret;
448 : }
449 :
450 : Datum
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. */
475 3 : if (entry->type == DSMR_ENTRY_TYPE_DSM &&
476 1 : entry->dsm.handle != DSM_HANDLE_INVALID)
477 1 : vals[2] = Int64GetDatum(entry->dsm.size);
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
485 0 : nulls[2] = true;
486 :
487 3 : tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, vals, nulls);
488 : }
489 2 : dshash_seq_term(&status);
490 :
491 2 : return (Datum) 0;
492 : }
|