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