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 3297 : DSMRegistryShmemSize(void)
118 : {
119 3297 : return MAXALIGN(sizeof(DSMRegistryCtxStruct));
120 : }
121 :
122 : void
123 1150 : DSMRegistryShmemInit(void)
124 : {
125 : bool found;
126 :
127 1150 : DSMRegistryCtx = (DSMRegistryCtxStruct *)
128 1150 : ShmemInitStruct("DSM Registry Data",
129 : DSMRegistryShmemSize(),
130 : &found);
131 :
132 1150 : if (!found)
133 : {
134 1150 : DSMRegistryCtx->dsah = DSA_HANDLE_INVALID;
135 1150 : DSMRegistryCtx->dshh = DSHASH_HANDLE_INVALID;
136 : }
137 1150 : }
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 135 : init_dsm_registry(void)
146 : {
147 : /* Quick exit if we already did this. */
148 135 : if (dsm_registry_table)
149 8 : return;
150 :
151 : /* Otherwise, use a lock to ensure only one process creates the table. */
152 127 : LWLockAcquire(DSMRegistryLock, LW_EXCLUSIVE);
153 :
154 127 : if (DSMRegistryCtx->dshh == DSHASH_HANDLE_INVALID)
155 : {
156 : /* Initialize dynamic shared hash table for registry. */
157 15 : dsm_registry_dsa = dsa_create(LWTRANCHE_DSM_REGISTRY_DSA);
158 15 : dsm_registry_table = dshash_create(dsm_registry_dsa, &dsh_params, NULL);
159 :
160 15 : dsa_pin(dsm_registry_dsa);
161 15 : dsa_pin_mapping(dsm_registry_dsa);
162 :
163 : /* Store handles in shared memory for other backends to use. */
164 15 : DSMRegistryCtx->dsah = dsa_get_handle(dsm_registry_dsa);
165 15 : DSMRegistryCtx->dshh = dshash_get_hash_table_handle(dsm_registry_table);
166 : }
167 : else
168 : {
169 : /* Attach to existing dynamic shared hash table. */
170 112 : dsm_registry_dsa = dsa_attach(DSMRegistryCtx->dsah);
171 112 : dsa_pin_mapping(dsm_registry_dsa);
172 112 : dsm_registry_table = dshash_attach(dsm_registry_dsa, &dsh_params,
173 112 : DSMRegistryCtx->dshh, NULL);
174 : }
175 :
176 127 : 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 117 : 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 : Assert(found);
198 :
199 117 : if (!name || *name == '\0')
200 0 : ereport(ERROR,
201 : (errmsg("DSM segment name cannot be empty")));
202 :
203 117 : if (strlen(name) >= offsetof(DSMRegistryEntry, type))
204 0 : ereport(ERROR,
205 : (errmsg("DSM segment name too long")));
206 :
207 117 : 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 117 : oldcontext = MemoryContextSwitchTo(TopMemoryContext);
213 :
214 : /* Connect to the registry. */
215 117 : init_dsm_registry();
216 :
217 117 : entry = dshash_find_or_insert(dsm_registry_table, name, found);
218 117 : state = &entry->dsm;
219 117 : if (!(*found))
220 : {
221 13 : entry->type = DSMR_ENTRY_TYPE_DSM;
222 13 : state->handle = DSM_HANDLE_INVALID;
223 13 : state->size = size;
224 : }
225 104 : 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 104 : else if (state->size != size)
229 0 : ereport(ERROR,
230 : (errmsg("requested DSM segment size does not match size of existing segment")));
231 :
232 117 : if (state->handle == DSM_HANDLE_INVALID)
233 : {
234 13 : *found = false;
235 :
236 : /* Initialize the segment. */
237 13 : seg = dsm_create(size, 0);
238 :
239 13 : if (init_callback)
240 13 : (*init_callback) (dsm_segment_address(seg), arg);
241 :
242 13 : dsm_pin_segment(seg);
243 13 : dsm_pin_mapping(seg);
244 13 : state->handle = dsm_segment_handle(seg);
245 : }
246 : else
247 : {
248 : /* If the existing segment is not already attached, attach it now. */
249 104 : seg = dsm_find_mapping(state->handle);
250 104 : if (seg == NULL)
251 : {
252 101 : seg = dsm_attach(state->handle);
253 101 : if (seg == NULL)
254 0 : elog(ERROR, "could not map dynamic shared memory segment");
255 :
256 101 : dsm_pin_mapping(seg);
257 : }
258 : }
259 :
260 117 : ret = dsm_segment_address(seg);
261 117 : dshash_release_lock(dsm_registry_table, entry);
262 117 : MemoryContextSwitchTo(oldcontext);
263 :
264 117 : 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 *
276 14 : GetNamedDSA(const char *name, bool *found)
277 : {
278 : DSMRegistryEntry *entry;
279 : MemoryContext oldcontext;
280 : dsa_area *ret;
281 : NamedDSAState *state;
282 :
283 : Assert(found);
284 :
285 14 : if (!name || *name == '\0')
286 0 : ereport(ERROR,
287 : (errmsg("DSA name cannot be empty")));
288 :
289 14 : 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 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);
300 14 : state = &entry->dsa;
301 14 : if (!(*found))
302 : {
303 3 : entry->type = DSMR_ENTRY_TYPE_DSA;
304 3 : state->handle = DSA_HANDLE_INVALID;
305 3 : state->tranche = -1;
306 : }
307 11 : 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 14 : if (state->tranche == -1)
312 : {
313 3 : *found = false;
314 :
315 : /* Initialize the LWLock tranche for the DSA. */
316 3 : state->tranche = LWLockNewTrancheId(name);
317 : }
318 :
319 14 : if (state->handle == DSA_HANDLE_INVALID)
320 : {
321 3 : *found = false;
322 :
323 : /* Initialize the DSA. */
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 : }
331 11 : 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 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 : Assert(params);
365 : Assert(found);
366 :
367 2 : if (!name || *name == '\0')
368 0 : ereport(ERROR,
369 : (errmsg("DSHash name cannot be empty")));
370 :
371 2 : 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 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);
382 2 : dsh_state = &entry->dsh;
383 2 : if (!(*found))
384 : {
385 1 : entry->type = DSMR_ENTRY_TYPE_DSH;
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)
391 0 : ereport(ERROR,
392 : (errmsg("requested DSHash does not match type of existing entry")));
393 :
394 2 : if (dsh_state->tranche == -1)
395 : {
396 1 : *found = false;
397 :
398 : /* Initialize the LWLock tranche for the hash table. */
399 1 : dsh_state->tranche = LWLockNewTrancheId(name);
400 : }
401 :
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. */
410 1 : dsa = dsa_create(dsh_state->tranche);
411 :
412 : /* Initialize the dshash table. */
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 :
417 1 : dsa_pin(dsa);
418 1 : dsa_pin_mapping(dsa);
419 :
420 : /* Store handles for other backends to use. */
421 1 : dsh_state->dsa_handle = dsa_get_handle(dsa);
422 1 : dsh_state->dsh_handle = dshash_get_hash_table_handle(ret);
423 : }
424 1 : 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 : 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 1 : dsa = dsa_attach(dsh_state->dsa_handle);
435 1 : dsa_pin_mapping(dsa);
436 :
437 : /* Attach to existing dshash table. */
438 1 : ret = dshash_attach(dsa, params, dsh_state->dsh_handle, NULL);
439 : }
440 :
441 2 : dshash_release_lock(dsm_registry_table, entry);
442 2 : MemoryContextSwitchTo(oldcontext);
443 :
444 2 : return ret;
445 : }
446 :
447 : Datum
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. */
472 3 : if (entry->type == DSMR_ENTRY_TYPE_DSM &&
473 1 : entry->dsm.handle != DSM_HANDLE_INVALID)
474 1 : vals[2] = Int64GetDatum(entry->dsm.size);
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
482 0 : nulls[2] = true;
483 :
484 3 : tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, vals, nulls);
485 : }
486 2 : dshash_seq_term(&status);
487 :
488 2 : return (Datum) 0;
489 : }
|