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 "lib/dshash.h"
44 : #include "storage/dsm_registry.h"
45 : #include "storage/lwlock.h"
46 : #include "storage/shmem.h"
47 : #include "utils/memutils.h"
48 :
49 : #define DSMR_NAME_LEN 128
50 :
51 : #define DSMR_DSA_TRANCHE_SUFFIX " DSA"
52 : #define DSMR_DSA_TRANCHE_SUFFIX_LEN (sizeof(DSMR_DSA_TRANCHE_SUFFIX) - 1)
53 : #define DSMR_DSA_TRANCHE_NAME_LEN (DSMR_NAME_LEN + DSMR_DSA_TRANCHE_SUFFIX_LEN)
54 :
55 : typedef struct DSMRegistryCtxStruct
56 : {
57 : dsa_handle dsah;
58 : dshash_table_handle dshh;
59 : } DSMRegistryCtxStruct;
60 :
61 : static DSMRegistryCtxStruct *DSMRegistryCtx;
62 :
63 : typedef struct NamedDSMState
64 : {
65 : dsm_handle handle;
66 : size_t size;
67 : } NamedDSMState;
68 :
69 : typedef struct NamedDSAState
70 : {
71 : dsa_handle handle;
72 : int tranche;
73 : char tranche_name[DSMR_DSA_TRANCHE_NAME_LEN];
74 : } NamedDSAState;
75 :
76 : typedef struct NamedDSHState
77 : {
78 : NamedDSAState dsa;
79 : dshash_table_handle handle;
80 : int tranche;
81 : char tranche_name[DSMR_NAME_LEN];
82 : } NamedDSHState;
83 :
84 : typedef enum DSMREntryType
85 : {
86 : DSMR_ENTRY_TYPE_DSM,
87 : DSMR_ENTRY_TYPE_DSA,
88 : DSMR_ENTRY_TYPE_DSH,
89 : } DSMREntryType;
90 :
91 : typedef struct DSMRegistryEntry
92 : {
93 : char name[DSMR_NAME_LEN];
94 : DSMREntryType type;
95 : union
96 : {
97 : NamedDSMState dsm;
98 : NamedDSAState dsa;
99 : NamedDSHState dsh;
100 : } data;
101 : } DSMRegistryEntry;
102 :
103 : static const dshash_parameters dsh_params = {
104 : offsetof(DSMRegistryEntry, type),
105 : sizeof(DSMRegistryEntry),
106 : dshash_strcmp,
107 : dshash_strhash,
108 : dshash_strcpy,
109 : LWTRANCHE_DSM_REGISTRY_HASH
110 : };
111 :
112 : static dsa_area *dsm_registry_dsa;
113 : static dshash_table *dsm_registry_table;
114 :
115 : Size
116 6078 : DSMRegistryShmemSize(void)
117 : {
118 6078 : return MAXALIGN(sizeof(DSMRegistryCtxStruct));
119 : }
120 :
121 : void
122 2126 : DSMRegistryShmemInit(void)
123 : {
124 : bool found;
125 :
126 2126 : DSMRegistryCtx = (DSMRegistryCtxStruct *)
127 2126 : ShmemInitStruct("DSM Registry Data",
128 : DSMRegistryShmemSize(),
129 : &found);
130 :
131 2126 : if (!found)
132 : {
133 2126 : DSMRegistryCtx->dsah = DSA_HANDLE_INVALID;
134 2126 : DSMRegistryCtx->dshh = DSHASH_HANDLE_INVALID;
135 : }
136 2126 : }
137 :
138 : /*
139 : * Initialize or attach to the dynamic shared hash table that stores the DSM
140 : * registry entries, if not already done. This must be called before accessing
141 : * the table.
142 : */
143 : static void
144 64 : init_dsm_registry(void)
145 : {
146 : /* Quick exit if we already did this. */
147 64 : if (dsm_registry_table)
148 12 : return;
149 :
150 : /* Otherwise, use a lock to ensure only one process creates the table. */
151 52 : LWLockAcquire(DSMRegistryLock, LW_EXCLUSIVE);
152 :
153 52 : if (DSMRegistryCtx->dshh == DSHASH_HANDLE_INVALID)
154 : {
155 : /* Initialize dynamic shared hash table for registry. */
156 18 : dsm_registry_dsa = dsa_create(LWTRANCHE_DSM_REGISTRY_DSA);
157 18 : dsa_pin(dsm_registry_dsa);
158 18 : dsa_pin_mapping(dsm_registry_dsa);
159 18 : dsm_registry_table = dshash_create(dsm_registry_dsa, &dsh_params, NULL);
160 :
161 : /* Store handles in shared memory for other backends to use. */
162 18 : DSMRegistryCtx->dsah = dsa_get_handle(dsm_registry_dsa);
163 18 : DSMRegistryCtx->dshh = dshash_get_hash_table_handle(dsm_registry_table);
164 : }
165 : else
166 : {
167 : /* Attach to existing dynamic shared hash table. */
168 34 : dsm_registry_dsa = dsa_attach(DSMRegistryCtx->dsah);
169 34 : dsa_pin_mapping(dsm_registry_dsa);
170 34 : dsm_registry_table = dshash_attach(dsm_registry_dsa, &dsh_params,
171 34 : DSMRegistryCtx->dshh, NULL);
172 : }
173 :
174 52 : LWLockRelease(DSMRegistryLock);
175 : }
176 :
177 : /*
178 : * Initialize or attach a named DSM segment.
179 : *
180 : * This routine returns the address of the segment. init_callback is called to
181 : * initialize the segment when it is first created.
182 : */
183 : void *
184 56 : GetNamedDSMSegment(const char *name, size_t size,
185 : void (*init_callback) (void *ptr), bool *found)
186 : {
187 : DSMRegistryEntry *entry;
188 : MemoryContext oldcontext;
189 : void *ret;
190 :
191 : Assert(found);
192 :
193 56 : if (!name || *name == '\0')
194 0 : ereport(ERROR,
195 : (errmsg("DSM segment name cannot be empty")));
196 :
197 56 : if (strlen(name) >= offsetof(DSMRegistryEntry, type))
198 0 : ereport(ERROR,
199 : (errmsg("DSM segment name too long")));
200 :
201 56 : if (size == 0)
202 0 : ereport(ERROR,
203 : (errmsg("DSM segment size must be nonzero")));
204 :
205 : /* Be sure any local memory allocated by DSM/DSA routines is persistent. */
206 56 : oldcontext = MemoryContextSwitchTo(TopMemoryContext);
207 :
208 : /* Connect to the registry. */
209 56 : init_dsm_registry();
210 :
211 56 : entry = dshash_find_or_insert(dsm_registry_table, name, found);
212 56 : if (!(*found))
213 : {
214 18 : NamedDSMState *state = &entry->data.dsm;
215 : dsm_segment *seg;
216 :
217 18 : entry->type = DSMR_ENTRY_TYPE_DSM;
218 :
219 : /* Initialize the segment. */
220 18 : seg = dsm_create(size, 0);
221 :
222 18 : dsm_pin_segment(seg);
223 18 : dsm_pin_mapping(seg);
224 18 : state->handle = dsm_segment_handle(seg);
225 18 : state->size = size;
226 18 : ret = dsm_segment_address(seg);
227 :
228 18 : if (init_callback)
229 18 : (*init_callback) (ret);
230 : }
231 38 : else if (entry->type != DSMR_ENTRY_TYPE_DSM)
232 0 : ereport(ERROR,
233 : (errmsg("requested DSM segment does not match type of existing entry")));
234 38 : else if (entry->data.dsm.size != size)
235 0 : ereport(ERROR,
236 : (errmsg("requested DSM segment size does not match size of existing segment")));
237 : else
238 : {
239 38 : NamedDSMState *state = &entry->data.dsm;
240 : dsm_segment *seg;
241 :
242 : /* If the existing segment is not already attached, attach it now. */
243 38 : seg = dsm_find_mapping(state->handle);
244 38 : if (seg == NULL)
245 : {
246 34 : seg = dsm_attach(state->handle);
247 34 : if (seg == NULL)
248 0 : elog(ERROR, "could not map dynamic shared memory segment");
249 :
250 34 : dsm_pin_mapping(seg);
251 : }
252 :
253 38 : ret = dsm_segment_address(seg);
254 : }
255 :
256 56 : dshash_release_lock(dsm_registry_table, entry);
257 56 : MemoryContextSwitchTo(oldcontext);
258 :
259 56 : return ret;
260 : }
261 :
262 : /*
263 : * Initialize or attach a named DSA.
264 : *
265 : * This routine returns a pointer to the DSA. A new LWLock tranche ID will be
266 : * generated if needed. Note that the lock tranche will be registered with the
267 : * provided name. Also note that this should be called at most once for a
268 : * given DSA in each backend.
269 : */
270 : dsa_area *
271 4 : GetNamedDSA(const char *name, bool *found)
272 : {
273 : DSMRegistryEntry *entry;
274 : MemoryContext oldcontext;
275 : dsa_area *ret;
276 :
277 : Assert(found);
278 :
279 4 : if (!name || *name == '\0')
280 0 : ereport(ERROR,
281 : (errmsg("DSA name cannot be empty")));
282 :
283 4 : if (strlen(name) >= offsetof(DSMRegistryEntry, type))
284 0 : ereport(ERROR,
285 : (errmsg("DSA name too long")));
286 :
287 : /* Be sure any local memory allocated by DSM/DSA routines is persistent. */
288 4 : oldcontext = MemoryContextSwitchTo(TopMemoryContext);
289 :
290 : /* Connect to the registry. */
291 4 : init_dsm_registry();
292 :
293 4 : entry = dshash_find_or_insert(dsm_registry_table, name, found);
294 4 : if (!(*found))
295 : {
296 2 : NamedDSAState *state = &entry->data.dsa;
297 :
298 2 : entry->type = DSMR_ENTRY_TYPE_DSA;
299 :
300 : /* Initialize the LWLock tranche for the DSA. */
301 2 : state->tranche = LWLockNewTrancheId();
302 2 : strcpy(state->tranche_name, name);
303 2 : LWLockRegisterTranche(state->tranche, 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, 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; new tranche IDs will be generated if needed. Note that
343 : * the DSA lock tranche will be registered with the provided name with " DSA"
344 : * appended. The dshash lock tranche will be registered with the provided
345 : * name. Also note that this should be called at most once for a given table
346 : * in each backend.
347 : */
348 : dshash_table *
349 4 : GetNamedDSHash(const char *name, const dshash_parameters *params, bool *found)
350 : {
351 : DSMRegistryEntry *entry;
352 : MemoryContext oldcontext;
353 : dshash_table *ret;
354 :
355 : Assert(params);
356 : Assert(found);
357 :
358 4 : if (!name || *name == '\0')
359 0 : ereport(ERROR,
360 : (errmsg("DSHash name cannot be empty")));
361 :
362 4 : if (strlen(name) >= offsetof(DSMRegistryEntry, type))
363 0 : ereport(ERROR,
364 : (errmsg("DSHash name too long")));
365 :
366 : /* Be sure any local memory allocated by DSM/DSA routines is persistent. */
367 4 : oldcontext = MemoryContextSwitchTo(TopMemoryContext);
368 :
369 : /* Connect to the registry. */
370 4 : init_dsm_registry();
371 :
372 4 : entry = dshash_find_or_insert(dsm_registry_table, name, found);
373 4 : if (!(*found))
374 : {
375 2 : NamedDSAState *dsa_state = &entry->data.dsh.dsa;
376 2 : NamedDSHState *dsh_state = &entry->data.dsh;
377 : dshash_parameters params_copy;
378 : dsa_area *dsa;
379 :
380 2 : entry->type = DSMR_ENTRY_TYPE_DSH;
381 :
382 : /* Initialize the LWLock tranche for the DSA. */
383 2 : dsa_state->tranche = LWLockNewTrancheId();
384 2 : sprintf(dsa_state->tranche_name, "%s%s", name, DSMR_DSA_TRANCHE_SUFFIX);
385 2 : LWLockRegisterTranche(dsa_state->tranche, dsa_state->tranche_name);
386 :
387 : /* Initialize the LWLock tranche for the dshash table. */
388 2 : dsh_state->tranche = LWLockNewTrancheId();
389 2 : strcpy(dsh_state->tranche_name, name);
390 2 : LWLockRegisterTranche(dsh_state->tranche, dsh_state->tranche_name);
391 :
392 : /* Initialize the DSA for the hash table. */
393 2 : dsa = dsa_create(dsa_state->tranche);
394 2 : dsa_pin(dsa);
395 2 : dsa_pin_mapping(dsa);
396 :
397 : /* Initialize the dshash table. */
398 2 : memcpy(¶ms_copy, params, sizeof(dshash_parameters));
399 2 : params_copy.tranche_id = dsh_state->tranche;
400 2 : ret = dshash_create(dsa, ¶ms_copy, NULL);
401 :
402 : /* Store handles for other backends to use. */
403 2 : dsa_state->handle = dsa_get_handle(dsa);
404 2 : dsh_state->handle = dshash_get_hash_table_handle(ret);
405 : }
406 2 : else if (entry->type != DSMR_ENTRY_TYPE_DSH)
407 0 : ereport(ERROR,
408 : (errmsg("requested DSHash does not match type of existing entry")));
409 : else
410 : {
411 2 : NamedDSAState *dsa_state = &entry->data.dsh.dsa;
412 2 : NamedDSHState *dsh_state = &entry->data.dsh;
413 : dsa_area *dsa;
414 :
415 : /* XXX: Should we verify params matches what table was created with? */
416 :
417 2 : if (dsa_is_attached(dsa_state->handle))
418 0 : ereport(ERROR,
419 : (errmsg("requested DSHash already attached to current process")));
420 :
421 : /* Initialize existing LWLock tranches for the DSA and dshash table. */
422 2 : LWLockRegisterTranche(dsa_state->tranche, dsa_state->tranche_name);
423 2 : LWLockRegisterTranche(dsh_state->tranche, dsh_state->tranche_name);
424 :
425 : /* Attach to existing DSA for the hash table. */
426 2 : dsa = dsa_attach(dsa_state->handle);
427 2 : dsa_pin_mapping(dsa);
428 :
429 : /* Attach to existing dshash table. */
430 2 : ret = dshash_attach(dsa, params, dsh_state->handle, NULL);
431 : }
432 :
433 4 : dshash_release_lock(dsm_registry_table, entry);
434 4 : MemoryContextSwitchTo(oldcontext);
435 :
436 4 : return ret;
437 : }
|