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