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