Branch data Line data Source code
1 : : /*--------------------------------------------------------------------------
2 : : *
3 : : * injection_points.c
4 : : * Code for testing injection points.
5 : : *
6 : : * Injection points are able to trigger user-defined callbacks in pre-defined
7 : : * code paths.
8 : : *
9 : : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
10 : : * Portions Copyright (c) 1994, Regents of the University of California
11 : : *
12 : : * IDENTIFICATION
13 : : * src/test/modules/injection_points/injection_points.c
14 : : *
15 : : * -------------------------------------------------------------------------
16 : : */
17 : :
18 : : #include "postgres.h"
19 : :
20 : : #include "fmgr.h"
21 : : #include "funcapi.h"
22 : : #include "injection_points.h"
23 : : #include "miscadmin.h"
24 : : #include "nodes/pg_list.h"
25 : : #include "nodes/value.h"
26 : : #include "storage/dsm_registry.h"
27 : : #include "storage/ipc.h"
28 : : #include "storage/lwlock.h"
29 : : #include "storage/shmem.h"
30 : : #include "storage/spin.h"
31 : : #include "utils/builtins.h"
32 : : #include "utils/guc.h"
33 : : #include "utils/injection_point.h"
34 : : #include "utils/memutils.h"
35 : : #include "utils/tuplestore.h"
36 : : #include "utils/wait_event.h"
37 : :
38 : 232 : PG_MODULE_MAGIC;
39 : :
40 : : /* Maximum number of waits usable in injection points at once */
41 : : #define INJ_MAX_WAIT 8
42 : : #define INJ_NAME_MAXLEN 64
43 : :
44 : : /* Thresholds of waits */
45 : : #define INJ_WAIT_INITIAL_US 10 /* 10us */
46 : : #define INJ_WAIT_MAX_US 100000 /* 100ms */
47 : :
48 : : /*
49 : : * List of injection points stored in TopMemoryContext attached
50 : : * locally to this process.
51 : : */
52 : : static List *inj_list_local = NIL;
53 : :
54 : : /*
55 : : * Shared state information for injection points.
56 : : *
57 : : * This state data can be initialized in two ways: dynamically with a DSM
58 : : * or when loading the module.
59 : : */
60 : : typedef struct InjectionPointSharedState
61 : : {
62 : : /* Protects access to other fields */
63 : : slock_t lock;
64 : :
65 : : /* Counters advancing when injection_points_wakeup() is called */
66 : : pg_atomic_uint32 wait_counts[INJ_MAX_WAIT];
67 : :
68 : : /* Names of injection points attached to wait counters */
69 : : char name[INJ_MAX_WAIT][INJ_NAME_MAXLEN];
70 : : } InjectionPointSharedState;
71 : :
72 : : /* Pointer to shared-memory state. */
73 : : static InjectionPointSharedState *inj_state = NULL;
74 : :
75 : : extern PGDLLEXPORT void injection_error(const char *name,
76 : : const void *private_data,
77 : : void *arg);
78 : : extern PGDLLEXPORT void injection_notice(const char *name,
79 : : const void *private_data,
80 : : void *arg);
81 : : extern PGDLLEXPORT void injection_wait(const char *name,
82 : : const void *private_data,
83 : : void *arg);
84 : :
85 : : /* track if injection points attached in this process are linked to it */
86 : : static bool injection_point_local = false;
87 : :
88 : : static void injection_shmem_request(void *arg);
89 : : static void injection_shmem_init(void *arg);
90 : :
91 : : static const ShmemCallbacks injection_shmem_callbacks = {
92 : : .request_fn = injection_shmem_request,
93 : : .init_fn = injection_shmem_init,
94 : : };
95 : :
96 : : /*
97 : : * Routine for shared memory area initialization, used as a callback
98 : : * when initializing dynamically with a DSM or when loading the module.
99 : : */
100 : : static void
101 : 19 : injection_point_init_state(void *ptr, void *arg)
102 : : {
103 : 19 : InjectionPointSharedState *state = (InjectionPointSharedState *) ptr;
104 : :
105 : 19 : SpinLockInit(&state->lock);
106 : 19 : memset(state->name, 0, sizeof(state->name));
107 [ + + ]: 171 : for (int i = 0; i < INJ_MAX_WAIT; i++)
108 : 152 : pg_atomic_init_u32(&state->wait_counts[i], 0);
109 : 19 : }
110 : :
111 : : static void
112 : 3 : injection_shmem_request(void *arg)
113 : : {
114 : 3 : ShmemRequestStruct(.name = "injection_points",
115 : : .size = sizeof(InjectionPointSharedState),
116 : : .ptr = (void **) &inj_state,
117 : : );
118 : 3 : }
119 : :
120 : : static void
121 : 3 : injection_shmem_init(void *arg)
122 : : {
123 : : /*
124 : : * First time through, so initialize. This is shared with the dynamic
125 : : * initialization using a DSM.
126 : : */
127 : 3 : injection_point_init_state(inj_state, NULL);
128 : 3 : }
129 : :
130 : : /*
131 : : * Initialize shared memory area for this module through DSM.
132 : : */
133 : : static void
134 : 155 : injection_init_shmem(void)
135 : : {
136 : : bool found;
137 : :
138 [ - + ]: 155 : if (inj_state != NULL)
139 : 0 : return;
140 : :
141 : 155 : inj_state = GetNamedDSMSegment("injection_points",
142 : : sizeof(InjectionPointSharedState),
143 : : injection_point_init_state,
144 : : &found, NULL);
145 : : }
146 : :
147 : : /*
148 : : * Check runtime conditions associated to an injection point.
149 : : *
150 : : * Returns true if the named injection point is allowed to run, and false
151 : : * otherwise.
152 : : */
153 : : static bool
154 : 315 : injection_point_allowed(const InjectionPointCondition *condition,
155 : : const char *arg)
156 : : {
157 : : /* Does not match the condition PID? */
158 [ + + ]: 315 : if ((condition->type & INJ_CONDITION_PID) &&
159 [ + + ]: 272 : MyProcPid != condition->pid)
160 : 140 : return false;
161 : :
162 : : /* Does not match the condition string? */
163 [ + + + + ]: 175 : if ((condition->type & INJ_CONDITION_STRING) &&
164 [ + + ]: 4 : (arg == NULL || strcmp(condition->str, arg) != 0))
165 : 2 : return false;
166 : :
167 : 173 : return true;
168 : : }
169 : :
170 : : /*
171 : : * before_shmem_exit callback to remove injection points linked to a
172 : : * specific process.
173 : : */
174 : : static void
175 : 77 : injection_points_cleanup(int code, Datum arg)
176 : : {
177 : : ListCell *lc;
178 : :
179 : : /* Leave if nothing is tracked locally */
180 [ - + ]: 77 : if (!injection_point_local)
181 : 0 : return;
182 : :
183 : : /* Detach all the local points */
184 [ + + + + : 231 : foreach(lc, inj_list_local)
+ + ]
185 : : {
186 : 154 : char *name = strVal(lfirst(lc));
187 : :
188 : 154 : (void) InjectionPointDetach(name);
189 : : }
190 : : }
191 : :
192 : : /* Set of callbacks available to be attached to an injection point. */
193 : : void
194 : 14 : injection_error(const char *name, const void *private_data, void *arg)
195 : : {
196 : 14 : const InjectionPointCondition *condition = private_data;
197 : 14 : char *argstr = arg;
198 : :
199 [ - + ]: 14 : if (!injection_point_allowed(condition, argstr))
200 : 0 : return;
201 : :
202 [ + + ]: 14 : if (argstr)
203 [ + - ]: 1 : elog(ERROR, "error triggered for injection point %s (%s)",
204 : : name, argstr);
205 : : else
206 [ + - ]: 13 : elog(ERROR, "error triggered for injection point %s", name);
207 : : }
208 : :
209 : : void
210 : 73 : injection_notice(const char *name, const void *private_data, void *arg)
211 : : {
212 : 73 : const InjectionPointCondition *condition = private_data;
213 : 73 : char *argstr = arg;
214 : :
215 [ + + ]: 73 : if (!injection_point_allowed(condition, argstr))
216 : 2 : return;
217 : :
218 [ + + ]: 71 : if (argstr)
219 [ + + ]: 4 : elog(NOTICE, "notice triggered for injection point %s (%s)",
220 : : name, argstr);
221 : : else
222 [ + - ]: 67 : elog(NOTICE, "notice triggered for injection point %s", name);
223 : : }
224 : :
225 : : /*
226 : : * Error cleanup callback for injection point waits.
227 : : */
228 : : static void
229 : 88 : injection_wait_cleanup(int code, Datum arg)
230 : : {
231 : 88 : int index = DatumGetInt32(arg);
232 : :
233 : 88 : SpinLockAcquire(&inj_state->lock);
234 : 88 : inj_state->name[index][0] = '\0';
235 : 88 : SpinLockRelease(&inj_state->lock);
236 : 88 : }
237 : :
238 : : /* Wait until injection_points_wakeup() is called */
239 : : void
240 : 228 : injection_wait(const char *name, const void *private_data, void *arg)
241 : : {
242 : 228 : uint32 old_wait_counts = 0;
243 : 228 : int index = -1;
244 : 228 : uint32 injection_wait_event = 0;
245 : 228 : const InjectionPointCondition *condition = private_data;
246 : 228 : char *argstr = arg;
247 : 228 : int delay_us = 0;
248 : :
249 [ + + ]: 228 : if (inj_state == NULL)
250 : 35 : injection_init_shmem();
251 : :
252 [ + + ]: 228 : if (!injection_point_allowed(condition, argstr))
253 : 140 : return;
254 : :
255 : : /*
256 : : * Use the injection point name for this custom wait event. Note that
257 : : * this custom wait event name is not released, but we don't care much for
258 : : * testing as this should be short-lived.
259 : : */
260 : 88 : injection_wait_event = WaitEventInjectionPointNew(name);
261 : :
262 : : /*
263 : : * Find a free slot to wait for, and register this injection point's name.
264 : : */
265 : 88 : SpinLockAcquire(&inj_state->lock);
266 [ + - ]: 121 : for (int i = 0; i < INJ_MAX_WAIT; i++)
267 : : {
268 [ + + ]: 121 : if (inj_state->name[i][0] == '\0')
269 : : {
270 : 88 : index = i;
271 : 88 : strlcpy(inj_state->name[i], name, INJ_NAME_MAXLEN);
272 : 88 : old_wait_counts = pg_atomic_read_u32(&inj_state->wait_counts[i]);
273 : 88 : break;
274 : : }
275 : : }
276 : 88 : SpinLockRelease(&inj_state->lock);
277 : :
278 [ - + ]: 88 : if (index < 0)
279 [ # # ]: 0 : elog(ERROR, "could not find free slot for wait of injection point %s",
280 : : name);
281 : :
282 : : /*
283 : : * Wait until the counter is bumped by injection_points_wakeup().
284 : : *
285 : : * This loop starts with a short delay for responsiveness, enlarged to
286 : : * ease the CPU workload in slower environments.
287 : : */
288 : 88 : delay_us = INJ_WAIT_INITIAL_US;
289 : :
290 : 88 : pgstat_report_wait_start(injection_wait_event);
291 [ + + ]: 88 : PG_ENSURE_ERROR_CLEANUP(injection_wait_cleanup, Int32GetDatum(index));
292 : : {
293 [ + + ]: 1427 : while (pg_atomic_read_u32(&inj_state->wait_counts[index]) == old_wait_counts)
294 : : {
295 [ + + ]: 1344 : CHECK_FOR_INTERRUPTS();
296 : 1339 : pg_usleep(delay_us);
297 [ + + ]: 1339 : if (delay_us < INJ_WAIT_MAX_US)
298 : 1185 : delay_us = Min(delay_us * 2, INJ_WAIT_MAX_US);
299 : : }
300 : : }
301 [ - + ]: 86 : PG_END_ENSURE_ERROR_CLEANUP(injection_wait_cleanup, Int32GetDatum(index));
302 : 83 : pgstat_report_wait_end();
303 : :
304 : : /* Remove this injection point from the waiters. */
305 : 83 : injection_wait_cleanup(0, Int32GetDatum(index));
306 : : }
307 : :
308 : : /*
309 : : * SQL function for creating an injection point.
310 : : */
311 : 170 : PG_FUNCTION_INFO_V1(injection_points_attach);
312 : : Datum
313 : 154 : injection_points_attach(PG_FUNCTION_ARGS)
314 : : {
315 : : char *name;
316 : : char *action;
317 : : char *str;
318 : : char *function;
319 : 154 : InjectionPointCondition condition = {0};
320 : :
321 [ + + ]: 154 : if (PG_ARGISNULL(0))
322 [ + - ]: 1 : elog(ERROR, "injection point name must not be null");
323 : :
324 [ + + ]: 153 : if (PG_ARGISNULL(1))
325 [ + - ]: 1 : elog(ERROR, "injection point action must not be null");
326 : :
327 : 152 : name = text_to_cstring(PG_GETARG_TEXT_PP(0));
328 : 152 : action = text_to_cstring(PG_GETARG_TEXT_PP(1));
329 : 304 : str = PG_ARGISNULL(2) ? NULL
330 [ + + ]: 152 : : text_to_cstring(PG_GETARG_TEXT_PP(2));
331 : :
332 [ + + ]: 152 : if (strcmp(action, "error") == 0)
333 : 23 : function = "injection_error";
334 [ + + ]: 129 : else if (strcmp(action, "notice") == 0)
335 : 32 : function = "injection_notice";
336 [ + + ]: 97 : else if (strcmp(action, "wait") == 0)
337 : 96 : function = "injection_wait";
338 : : else
339 [ + - ]: 1 : elog(ERROR, "incorrect action \"%s\" for injection point creation", action);
340 : :
341 [ + + ]: 151 : if (injection_point_local)
342 : : {
343 : 107 : condition.type |= INJ_CONDITION_PID;
344 : 107 : condition.pid = MyProcPid;
345 : : }
346 : :
347 [ + + ]: 151 : if (str)
348 : : {
349 [ + + ]: 5 : if (str[0] == '\0')
350 [ + - ]: 1 : elog(ERROR, "injection point condition string must not be empty");
351 [ + + ]: 4 : if (strlen(str) >= INJ_DATA_MAXLEN)
352 [ + - ]: 1 : elog(ERROR, "injection point condition string too long (maximum of %d characters)",
353 : : INJ_DATA_MAXLEN - 1);
354 : 3 : condition.type |= INJ_CONDITION_STRING;
355 : 3 : strlcpy(condition.str, str, INJ_DATA_MAXLEN);
356 : : }
357 : :
358 : 149 : InjectionPointAttach(name, "injection_points", function, &condition,
359 : : sizeof(InjectionPointCondition));
360 : :
361 [ + + ]: 149 : if (injection_point_local)
362 : : {
363 : : MemoryContext oldctx;
364 : :
365 : : /* Local injection point, so track it for automated cleanup */
366 : 107 : oldctx = MemoryContextSwitchTo(TopMemoryContext);
367 : 107 : inj_list_local = lappend(inj_list_local, makeString(pstrdup(name)));
368 : 107 : MemoryContextSwitchTo(oldctx);
369 : : }
370 : :
371 : 149 : PG_RETURN_VOID();
372 : : }
373 : :
374 : : /*
375 : : * SQL function for creating an injection point with library name, function
376 : : * name and private data.
377 : : */
378 : 57 : PG_FUNCTION_INFO_V1(injection_points_attach_func);
379 : : Datum
380 : 8 : injection_points_attach_func(PG_FUNCTION_ARGS)
381 : : {
382 : : char *name;
383 : : char *lib_name;
384 : : char *function;
385 : 8 : bytea *private_data = NULL;
386 : 8 : int private_data_size = 0;
387 : :
388 [ + + ]: 8 : if (PG_ARGISNULL(0))
389 [ + - ]: 1 : elog(ERROR, "injection point name cannot be NULL");
390 [ + + ]: 7 : if (PG_ARGISNULL(1))
391 [ + - ]: 1 : elog(ERROR, "injection point library cannot be NULL");
392 [ + + ]: 6 : if (PG_ARGISNULL(2))
393 [ + - ]: 1 : elog(ERROR, "injection point function cannot be NULL");
394 : :
395 : 5 : name = text_to_cstring(PG_GETARG_TEXT_PP(0));
396 : 5 : lib_name = text_to_cstring(PG_GETARG_TEXT_PP(1));
397 : 5 : function = text_to_cstring(PG_GETARG_TEXT_PP(2));
398 : :
399 [ + + ]: 5 : if (!PG_ARGISNULL(3))
400 : : {
401 : 1 : private_data = PG_GETARG_BYTEA_PP(3);
402 : 1 : private_data_size = VARSIZE_ANY_EXHDR(private_data);
403 : : }
404 : :
405 [ + + ]: 5 : if (private_data != NULL)
406 : 1 : InjectionPointAttach(name, lib_name, function, VARDATA_ANY(private_data),
407 : : private_data_size);
408 : : else
409 : 4 : InjectionPointAttach(name, lib_name, function, NULL,
410 : : 0);
411 : 1 : PG_RETURN_VOID();
412 : : }
413 : :
414 : : /*
415 : : * SQL function for loading an injection point.
416 : : */
417 : 57 : PG_FUNCTION_INFO_V1(injection_points_load);
418 : : Datum
419 : 2 : injection_points_load(PG_FUNCTION_ARGS)
420 : : {
421 : 2 : char *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
422 : :
423 [ + + ]: 2 : if (inj_state == NULL)
424 : 1 : injection_init_shmem();
425 : :
426 : 2 : INJECTION_POINT_LOAD(name);
427 : :
428 : 2 : PG_RETURN_VOID();
429 : : }
430 : :
431 : : /*
432 : : * SQL function for triggering an injection point.
433 : : */
434 : 66 : PG_FUNCTION_INFO_V1(injection_points_run);
435 : : Datum
436 : 35 : injection_points_run(PG_FUNCTION_ARGS)
437 : : {
438 : : char *name;
439 : 35 : char *arg = NULL;
440 : :
441 [ + + ]: 35 : if (PG_ARGISNULL(0))
442 : 1 : PG_RETURN_VOID();
443 : 34 : name = text_to_cstring(PG_GETARG_TEXT_PP(0));
444 : :
445 [ + + ]: 34 : if (!PG_ARGISNULL(1))
446 : 4 : arg = text_to_cstring(PG_GETARG_TEXT_PP(1));
447 : :
448 : 34 : INJECTION_POINT(name, arg);
449 : :
450 : 26 : PG_RETURN_VOID();
451 : : }
452 : :
453 : : /*
454 : : * SQL function for triggering an injection point from cache.
455 : : */
456 : 58 : PG_FUNCTION_INFO_V1(injection_points_cached);
457 : : Datum
458 : 5 : injection_points_cached(PG_FUNCTION_ARGS)
459 : : {
460 : : char *name;
461 : 5 : char *arg = NULL;
462 : :
463 [ + + ]: 5 : if (PG_ARGISNULL(0))
464 : 1 : PG_RETURN_VOID();
465 : 4 : name = text_to_cstring(PG_GETARG_TEXT_PP(0));
466 : :
467 [ + + ]: 4 : if (!PG_ARGISNULL(1))
468 : 1 : arg = text_to_cstring(PG_GETARG_TEXT_PP(1));
469 : :
470 : 4 : INJECTION_POINT_CACHED(name, arg);
471 : :
472 : 4 : PG_RETURN_VOID();
473 : : }
474 : :
475 : : /*
476 : : * SQL function for waking up an injection point waiting in injection_wait().
477 : : */
478 : 143 : PG_FUNCTION_INFO_V1(injection_points_wakeup);
479 : : Datum
480 : 90 : injection_points_wakeup(PG_FUNCTION_ARGS)
481 : : {
482 : 90 : char *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
483 : 90 : int index = -1;
484 : :
485 [ + + ]: 90 : if (inj_state == NULL)
486 : 64 : injection_init_shmem();
487 : :
488 : : /* Find the injection point then bump its wait counter */
489 : 90 : SpinLockAcquire(&inj_state->lock);
490 [ + + ]: 131 : for (int i = 0; i < INJ_MAX_WAIT; i++)
491 : : {
492 [ + + ]: 130 : if (strcmp(name, inj_state->name[i]) == 0)
493 : : {
494 : 89 : index = i;
495 : 89 : break;
496 : : }
497 : : }
498 [ + + ]: 90 : if (index < 0)
499 : : {
500 : 1 : SpinLockRelease(&inj_state->lock);
501 [ + - ]: 1 : elog(ERROR, "could not find injection point %s to wake up", name);
502 : : }
503 : 89 : SpinLockRelease(&inj_state->lock);
504 : :
505 : 89 : pg_atomic_fetch_add_u32(&inj_state->wait_counts[index], 1);
506 : 89 : PG_RETURN_VOID();
507 : : }
508 : :
509 : : /*
510 : : * injection_points_set_local
511 : : *
512 : : * Track if any injection point created in this process ought to run only
513 : : * in this process. Such injection points are detached automatically when
514 : : * this process exits. This is useful to make test suites concurrent-safe.
515 : : */
516 : 133 : PG_FUNCTION_INFO_V1(injection_points_set_local);
517 : : Datum
518 : 77 : injection_points_set_local(PG_FUNCTION_ARGS)
519 : : {
520 : : /* Enable flag to add a runtime condition based on this process ID */
521 : 77 : injection_point_local = true;
522 : :
523 [ + + ]: 77 : if (inj_state == NULL)
524 : 55 : injection_init_shmem();
525 : :
526 : : /*
527 : : * Register a before_shmem_exit callback to remove any injection points
528 : : * linked to this process.
529 : : */
530 : 77 : before_shmem_exit(injection_points_cleanup, (Datum) 0);
531 : :
532 : 77 : PG_RETURN_VOID();
533 : : }
534 : :
535 : : /*
536 : : * SQL function for dropping an injection point.
537 : : */
538 : 153 : PG_FUNCTION_INFO_V1(injection_points_detach);
539 : : Datum
540 : 126 : injection_points_detach(PG_FUNCTION_ARGS)
541 : : {
542 : 126 : char *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
543 : :
544 [ + + ]: 126 : if (!InjectionPointDetach(name))
545 [ + - ]: 1 : elog(ERROR, "could not detach injection point \"%s\"", name);
546 : :
547 : : /* Remove point from local list, if required */
548 [ + + ]: 125 : if (inj_list_local != NIL)
549 : : {
550 : : MemoryContext oldctx;
551 : :
552 : 31 : oldctx = MemoryContextSwitchTo(TopMemoryContext);
553 : 31 : inj_list_local = list_delete(inj_list_local, makeString(name));
554 : 31 : MemoryContextSwitchTo(oldctx);
555 : : }
556 : :
557 : 125 : PG_RETURN_VOID();
558 : : }
559 : :
560 : : /*
561 : : * SQL function for listing all the injection points attached.
562 : : */
563 : 58 : PG_FUNCTION_INFO_V1(injection_points_list);
564 : : Datum
565 : 3 : injection_points_list(PG_FUNCTION_ARGS)
566 : : {
567 : : #define NUM_INJECTION_POINTS_LIST 3
568 : 3 : ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
569 : : List *inj_points;
570 : : ListCell *lc;
571 : :
572 : : /* Build a tuplestore to return our results in */
573 : 3 : InitMaterializedSRF(fcinfo, 0);
574 : :
575 : 3 : inj_points = InjectionPointList();
576 : :
577 [ + + + + : 7 : foreach(lc, inj_points)
+ + ]
578 : : {
579 : : Datum values[NUM_INJECTION_POINTS_LIST];
580 : : bool nulls[NUM_INJECTION_POINTS_LIST];
581 : 4 : InjectionPointData *inj_point = lfirst(lc);
582 : :
583 : 4 : memset(values, 0, sizeof(values));
584 : 4 : memset(nulls, 0, sizeof(nulls));
585 : :
586 : 4 : values[0] = PointerGetDatum(cstring_to_text(inj_point->name));
587 : 4 : values[1] = PointerGetDatum(cstring_to_text(inj_point->library));
588 : 4 : values[2] = PointerGetDatum(cstring_to_text(inj_point->function));
589 : :
590 : : /* shove row into tuplestore */
591 : 4 : tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
592 : : }
593 : :
594 : 3 : return (Datum) 0;
595 : : #undef NUM_INJECTION_POINTS_LIST
596 : : }
597 : :
598 : : void
599 : 232 : _PG_init(void)
600 : : {
601 [ + + ]: 232 : if (!process_shared_preload_libraries_in_progress)
602 : 229 : return;
603 : :
604 : 3 : RegisterShmemCallbacks(&injection_shmem_callbacks);
605 : : }
|