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 "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 : :
916 michael@paquier.xyz 38 :CBC 203 : 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
223 nathan@postgresql.or 101 : 17 : injection_point_init_state(void *ptr, void *arg)
102 : : {
874 michael@paquier.xyz 103 : 17 : InjectionPointSharedState *state = (InjectionPointSharedState *) ptr;
104 : :
105 : 17 : SpinLockInit(&state->lock);
106 : 17 : memset(state->name, 0, sizeof(state->name));
18 michael@paquier.xyz 107 [ + + ]:GNC 153 : for (int i = 0; i < INJ_MAX_WAIT; i++)
108 : 136 : pg_atomic_init_u32(&state->wait_counts[i], 0);
874 michael@paquier.xyz 109 :CBC 17 : }
110 : :
111 : : static void
111 heikki.linnakangas@i 112 : 3 : injection_shmem_request(void *arg)
113 : : {
114 : 3 : ShmemRequestStruct(.name = "injection_points",
115 : : .size = sizeof(InjectionPointSharedState),
116 : : .ptr = (void **) &inj_state,
117 : : );
702 michael@paquier.xyz 118 : 3 : }
119 : :
120 : : static void
111 heikki.linnakangas@i 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);
702 michael@paquier.xyz 128 : 3 : }
129 : :
130 : : /*
131 : : * Initialize shared memory area for this module through DSM.
132 : : */
133 : : static void
874 134 : 140 : injection_init_shmem(void)
135 : : {
136 : : bool found;
137 : :
138 [ - + ]: 140 : if (inj_state != NULL)
874 michael@paquier.xyz 139 :UBC 0 : return;
140 : :
874 michael@paquier.xyz 141 :CBC 140 : 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
195 peter@eisentraut.org 154 : 290 : injection_point_allowed(const InjectionPointCondition *condition)
155 : : {
839 michael@paquier.xyz 156 : 290 : bool result = true;
157 : :
805 158 [ + + - ]: 290 : switch (condition->type)
159 : : {
160 : 254 : case INJ_CONDITION_PID:
839 161 [ + + ]: 254 : if (MyProcPid != condition->pid)
162 : 127 : result = false;
805 163 : 254 : break;
164 : 36 : case INJ_CONDITION_ALWAYS:
165 : 36 : break;
166 : : }
167 : :
839 168 : 290 : return result;
169 : : }
170 : :
171 : : /*
172 : : * before_shmem_exit callback to remove injection points linked to a
173 : : * specific process.
174 : : */
175 : : static void
176 : 72 : injection_points_cleanup(int code, Datum arg)
177 : : {
178 : : ListCell *lc;
179 : :
180 : : /* Leave if nothing is tracked locally */
181 [ - + ]: 72 : if (!injection_point_local)
839 michael@paquier.xyz 182 :UBC 0 : return;
183 : :
184 : : /* Detach all the local points */
805 michael@paquier.xyz 185 [ + + + + :CBC 221 : foreach(lc, inj_list_local)
+ + ]
186 : : {
187 : 149 : char *name = strVal(lfirst(lc));
188 : :
189 : 149 : (void) InjectionPointDetach(name);
190 : : }
191 : : }
192 : :
193 : : /* Set of callbacks available to be attached to an injection point. */
194 : : void
442 195 : 12 : injection_error(const char *name, const void *private_data, void *arg)
196 : : {
195 peter@eisentraut.org 197 : 12 : const InjectionPointCondition *condition = private_data;
198 : 12 : char *argstr = arg;
199 : :
805 michael@paquier.xyz 200 [ - + ]: 12 : if (!injection_point_allowed(condition))
839 michael@paquier.xyz 201 :UBC 0 : return;
202 : :
442 michael@paquier.xyz 203 [ + + ]:CBC 12 : if (argstr)
204 [ + - ]: 1 : elog(ERROR, "error triggered for injection point %s (%s)",
205 : : name, argstr);
206 : : else
207 [ + - ]: 11 : elog(ERROR, "error triggered for injection point %s", name);
208 : : }
209 : :
210 : : void
211 : 70 : injection_notice(const char *name, const void *private_data, void *arg)
212 : : {
195 peter@eisentraut.org 213 : 70 : const InjectionPointCondition *condition = private_data;
214 : 70 : char *argstr = arg;
215 : :
805 michael@paquier.xyz 216 [ - + ]: 70 : if (!injection_point_allowed(condition))
839 michael@paquier.xyz 217 :UBC 0 : return;
218 : :
442 michael@paquier.xyz 219 [ + + ]:CBC 70 : if (argstr)
220 [ + + ]: 3 : elog(NOTICE, "notice triggered for injection point %s (%s)",
221 : : name, argstr);
222 : : else
223 [ + - ]: 67 : elog(NOTICE, "notice triggered for injection point %s", name);
224 : : }
225 : :
226 : : /*
227 : : * Error cleanup callback for injection point waits.
228 : : */
229 : : static void
3 230 : 81 : injection_wait_cleanup(int code, Datum arg)
231 : : {
232 : 81 : int index = DatumGetInt32(arg);
233 : :
234 : 81 : SpinLockAcquire(&inj_state->lock);
235 : 81 : inj_state->name[index][0] = '\0';
236 : 81 : SpinLockRelease(&inj_state->lock);
237 : 81 : }
238 : :
239 : : /* Wait until injection_points_wakeup() is called */
240 : : void
442 241 : 208 : injection_wait(const char *name, const void *private_data, void *arg)
242 : : {
874 243 : 208 : uint32 old_wait_counts = 0;
244 : 208 : int index = -1;
245 : 208 : uint32 injection_wait_event = 0;
195 peter@eisentraut.org 246 : 208 : const InjectionPointCondition *condition = private_data;
18 michael@paquier.xyz 247 :GNC 208 : int delay_us = 0;
248 : :
874 michael@paquier.xyz 249 [ + + ]:CBC 208 : if (inj_state == NULL)
250 : 31 : injection_init_shmem();
251 : :
805 252 [ + + ]: 208 : if (!injection_point_allowed(condition))
839 253 : 127 : 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 : : */
759 noah@leadboat.com 260 : 81 : injection_wait_event = WaitEventInjectionPointNew(name);
261 : :
262 : : /*
263 : : * Find a free slot to wait for, and register this injection point's name.
264 : : */
874 michael@paquier.xyz 265 : 81 : SpinLockAcquire(&inj_state->lock);
266 [ + - ]: 114 : for (int i = 0; i < INJ_MAX_WAIT; i++)
267 : : {
268 [ + + ]: 114 : if (inj_state->name[i][0] == '\0')
269 : : {
270 : 81 : index = i;
271 : 81 : strlcpy(inj_state->name[i], name, INJ_NAME_MAXLEN);
18 michael@paquier.xyz 272 :GNC 81 : old_wait_counts = pg_atomic_read_u32(&inj_state->wait_counts[i]);
874 michael@paquier.xyz 273 :CBC 81 : break;
274 : : }
275 : : }
276 : 81 : SpinLockRelease(&inj_state->lock);
277 : :
278 [ - + ]: 81 : if (index < 0)
18 michael@paquier.xyz 279 [ # # ]:UNC 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 : : */
18 michael@paquier.xyz 288 :GNC 81 : delay_us = INJ_WAIT_INITIAL_US;
289 : :
290 : 81 : pgstat_report_wait_start(injection_wait_event);
3 michael@paquier.xyz 291 [ + + ]:CBC 81 : PG_ENSURE_ERROR_CLEANUP(injection_wait_cleanup, Int32GetDatum(index));
292 : : {
3 michael@paquier.xyz 293 [ + + ]:GNC 1225 : while (pg_atomic_read_u32(&inj_state->wait_counts[index]) == old_wait_counts)
3 michael@paquier.xyz 294 :ECB (99) : {
3 michael@paquier.xyz 295 [ + + ]:GNC 1149 : CHECK_FOR_INTERRUPTS();
296 : 1144 : pg_usleep(delay_us);
297 [ + + ]: 1144 : if (delay_us < INJ_WAIT_MAX_US)
298 : 1088 : delay_us = Min(delay_us * 2, INJ_WAIT_MAX_US);
299 : : }
300 : : }
3 michael@paquier.xyz 301 [ - + ]:CBC 79 : PG_END_ENSURE_ERROR_CLEANUP(injection_wait_cleanup, Int32GetDatum(index));
18 michael@paquier.xyz 302 :GNC 76 : pgstat_report_wait_end();
303 : :
304 : : /* Remove this injection point from the waiters. */
3 michael@paquier.xyz 305 :CBC 76 : injection_wait_cleanup(0, Int32GetDatum(index));
306 : : }
307 : :
308 : : /*
309 : : * SQL function for creating an injection point.
310 : : */
916 311 : 155 : PG_FUNCTION_INFO_V1(injection_points_attach);
312 : : Datum
313 : 136 : injection_points_attach(PG_FUNCTION_ARGS)
314 : : {
315 : 136 : char *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
316 : 136 : char *action = text_to_cstring(PG_GETARG_TEXT_PP(1));
317 : : char *function;
805 318 : 136 : InjectionPointCondition condition = {0};
319 : :
916 320 [ + + ]: 136 : if (strcmp(action, "error") == 0)
321 : 17 : function = "injection_error";
322 [ + + ]: 119 : else if (strcmp(action, "notice") == 0)
323 : 31 : function = "injection_notice";
874 324 [ + + ]: 88 : else if (strcmp(action, "wait") == 0)
325 : 87 : function = "injection_wait";
326 : : else
916 327 [ + - ]: 1 : elog(ERROR, "incorrect action \"%s\" for injection point creation", action);
328 : :
839 329 [ + + ]: 135 : if (injection_point_local)
330 : : {
805 331 : 102 : condition.type = INJ_CONDITION_PID;
332 : 102 : condition.pid = MyProcPid;
333 : : }
334 : :
335 : 135 : InjectionPointAttach(name, "injection_points", function, &condition,
336 : : sizeof(InjectionPointCondition));
337 : :
338 [ + + ]: 135 : if (injection_point_local)
339 : : {
340 : : MemoryContext oldctx;
341 : :
342 : : /* Local injection point, so track it for automated cleanup */
343 : 102 : oldctx = MemoryContextSwitchTo(TopMemoryContext);
344 : 102 : inj_list_local = lappend(inj_list_local, makeString(pstrdup(name)));
345 : 102 : MemoryContextSwitchTo(oldctx);
346 : : }
347 : :
916 348 : 135 : PG_RETURN_VOID();
349 : : }
350 : :
351 : : /*
352 : : * SQL function for creating an injection point with library name, function
353 : : * name and private data.
354 : : */
258 355 : 55 : PG_FUNCTION_INFO_V1(injection_points_attach_func);
356 : : Datum
357 : 8 : injection_points_attach_func(PG_FUNCTION_ARGS)
358 : : {
359 : : char *name;
360 : : char *lib_name;
361 : : char *function;
362 : 8 : bytea *private_data = NULL;
363 : 8 : int private_data_size = 0;
364 : :
365 [ + + ]: 8 : if (PG_ARGISNULL(0))
366 [ + - ]: 1 : elog(ERROR, "injection point name cannot be NULL");
367 [ + + ]: 7 : if (PG_ARGISNULL(1))
368 [ + - ]: 1 : elog(ERROR, "injection point library cannot be NULL");
369 [ + + ]: 6 : if (PG_ARGISNULL(2))
370 [ + - ]: 1 : elog(ERROR, "injection point function cannot be NULL");
371 : :
372 : 5 : name = text_to_cstring(PG_GETARG_TEXT_PP(0));
373 : 5 : lib_name = text_to_cstring(PG_GETARG_TEXT_PP(1));
374 : 5 : function = text_to_cstring(PG_GETARG_TEXT_PP(2));
375 : :
376 [ + + ]: 5 : if (!PG_ARGISNULL(3))
377 : : {
378 : 1 : private_data = PG_GETARG_BYTEA_PP(3);
379 : 1 : private_data_size = VARSIZE_ANY_EXHDR(private_data);
380 : : }
381 : :
382 [ + + ]: 5 : if (private_data != NULL)
383 : 1 : InjectionPointAttach(name, lib_name, function, VARDATA_ANY(private_data),
384 : : private_data_size);
385 : : else
386 : 4 : InjectionPointAttach(name, lib_name, function, NULL,
387 : : 0);
388 : 1 : PG_RETURN_VOID();
389 : : }
390 : :
391 : : /*
392 : : * SQL function for loading an injection point.
393 : : */
751 394 : 55 : PG_FUNCTION_INFO_V1(injection_points_load);
395 : : Datum
396 : 2 : injection_points_load(PG_FUNCTION_ARGS)
397 : : {
398 : 2 : char *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
399 : :
400 [ + + ]: 2 : if (inj_state == NULL)
401 : 1 : injection_init_shmem();
402 : :
403 : 2 : INJECTION_POINT_LOAD(name);
404 : :
405 : 2 : PG_RETURN_VOID();
406 : : }
407 : :
408 : : /*
409 : : * SQL function for triggering an injection point.
410 : : */
916 411 : 64 : PG_FUNCTION_INFO_V1(injection_points_run);
412 : : Datum
413 : 32 : injection_points_run(PG_FUNCTION_ARGS)
414 : : {
415 : : char *name;
442 416 : 32 : char *arg = NULL;
417 : :
418 [ + + ]: 32 : if (PG_ARGISNULL(0))
419 : 1 : PG_RETURN_VOID();
420 : 31 : name = text_to_cstring(PG_GETARG_TEXT_PP(0));
421 : :
422 [ + + ]: 31 : if (!PG_ARGISNULL(1))
423 : 2 : arg = text_to_cstring(PG_GETARG_TEXT_PP(1));
424 : :
425 : 31 : INJECTION_POINT(name, arg);
426 : :
916 427 : 23 : PG_RETURN_VOID();
428 : : }
429 : :
430 : : /*
431 : : * SQL function for triggering an injection point from cache.
432 : : */
738 433 : 56 : PG_FUNCTION_INFO_V1(injection_points_cached);
434 : : Datum
435 : 5 : injection_points_cached(PG_FUNCTION_ARGS)
436 : : {
437 : : char *name;
442 438 : 5 : char *arg = NULL;
439 : :
440 [ + + ]: 5 : if (PG_ARGISNULL(0))
441 : 1 : PG_RETURN_VOID();
442 : 4 : name = text_to_cstring(PG_GETARG_TEXT_PP(0));
443 : :
444 [ + + ]: 4 : if (!PG_ARGISNULL(1))
445 : 1 : arg = text_to_cstring(PG_GETARG_TEXT_PP(1));
446 : :
447 : 4 : INJECTION_POINT_CACHED(name, arg);
448 : :
738 449 : 4 : PG_RETURN_VOID();
450 : : }
451 : :
452 : : /*
453 : : * SQL function for waking up an injection point waiting in injection_wait().
454 : : */
874 455 : 134 : PG_FUNCTION_INFO_V1(injection_points_wakeup);
456 : : Datum
457 : 83 : injection_points_wakeup(PG_FUNCTION_ARGS)
458 : : {
459 : 83 : char *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
460 : 83 : int index = -1;
461 : :
462 [ + + ]: 83 : if (inj_state == NULL)
463 : 57 : injection_init_shmem();
464 : :
465 : : /* Find the injection point then bump its wait counter */
466 : 83 : SpinLockAcquire(&inj_state->lock);
467 [ + + ]: 124 : for (int i = 0; i < INJ_MAX_WAIT; i++)
468 : : {
469 [ + + ]: 123 : if (strcmp(name, inj_state->name[i]) == 0)
470 : : {
471 : 82 : index = i;
472 : 82 : break;
473 : : }
474 : : }
475 [ + + ]: 83 : if (index < 0)
476 : : {
477 : 1 : SpinLockRelease(&inj_state->lock);
478 [ + - ]: 1 : elog(ERROR, "could not find injection point %s to wake up", name);
479 : : }
480 : 82 : SpinLockRelease(&inj_state->lock);
481 : :
18 michael@paquier.xyz 482 :GNC 82 : pg_atomic_fetch_add_u32(&inj_state->wait_counts[index], 1);
874 michael@paquier.xyz 483 :CBC 82 : PG_RETURN_VOID();
484 : : }
485 : :
486 : : /*
487 : : * injection_points_set_local
488 : : *
489 : : * Track if any injection point created in this process ought to run only
490 : : * in this process. Such injection points are detached automatically when
491 : : * this process exits. This is useful to make test suites concurrent-safe.
492 : : */
839 493 : 126 : PG_FUNCTION_INFO_V1(injection_points_set_local);
494 : : Datum
495 : 72 : injection_points_set_local(PG_FUNCTION_ARGS)
496 : : {
497 : : /* Enable flag to add a runtime condition based on this process ID */
498 : 72 : injection_point_local = true;
499 : :
500 [ + + ]: 72 : if (inj_state == NULL)
501 : 51 : injection_init_shmem();
502 : :
503 : : /*
504 : : * Register a before_shmem_exit callback to remove any injection points
505 : : * linked to this process.
506 : : */
507 : 72 : before_shmem_exit(injection_points_cleanup, (Datum) 0);
508 : :
509 : 72 : PG_RETURN_VOID();
510 : : }
511 : :
512 : : /*
513 : : * SQL function for dropping an injection point.
514 : : */
916 515 : 144 : PG_FUNCTION_INFO_V1(injection_points_detach);
516 : : Datum
517 : 118 : injection_points_detach(PG_FUNCTION_ARGS)
518 : : {
519 : 118 : char *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
520 : :
805 521 [ + + ]: 118 : if (!InjectionPointDetach(name))
522 [ + - ]: 1 : elog(ERROR, "could not detach injection point \"%s\"", name);
523 : :
524 : : /* Remove point from local list, if required */
525 [ + + ]: 117 : if (inj_list_local != NIL)
526 : : {
527 : : MemoryContext oldctx;
528 : :
529 : 31 : oldctx = MemoryContextSwitchTo(TopMemoryContext);
530 : 31 : inj_list_local = list_delete(inj_list_local, makeString(name));
531 : 31 : MemoryContextSwitchTo(oldctx);
532 : : }
533 : :
916 534 : 117 : PG_RETURN_VOID();
535 : : }
536 : :
537 : : /*
538 : : * SQL function for listing all the injection points attached.
539 : : */
381 540 : 56 : PG_FUNCTION_INFO_V1(injection_points_list);
541 : : Datum
542 : 3 : injection_points_list(PG_FUNCTION_ARGS)
543 : : {
544 : : #define NUM_INJECTION_POINTS_LIST 3
545 : 3 : ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
546 : : List *inj_points;
547 : : ListCell *lc;
548 : :
549 : : /* Build a tuplestore to return our results in */
550 : 3 : InitMaterializedSRF(fcinfo, 0);
551 : :
552 : 3 : inj_points = InjectionPointList();
553 : :
554 [ + + + + : 7 : foreach(lc, inj_points)
+ + ]
555 : : {
556 : : Datum values[NUM_INJECTION_POINTS_LIST];
557 : : bool nulls[NUM_INJECTION_POINTS_LIST];
558 : 4 : InjectionPointData *inj_point = lfirst(lc);
559 : :
560 : 4 : memset(values, 0, sizeof(values));
561 : 4 : memset(nulls, 0, sizeof(nulls));
562 : :
563 : 4 : values[0] = PointerGetDatum(cstring_to_text(inj_point->name));
564 : 4 : values[1] = PointerGetDatum(cstring_to_text(inj_point->library));
565 : 4 : values[2] = PointerGetDatum(cstring_to_text(inj_point->function));
566 : :
567 : : /* shove row into tuplestore */
568 : 4 : tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
569 : : }
570 : :
571 : 3 : return (Datum) 0;
572 : : #undef NUM_INJECTION_POINTS_LIST
573 : : }
574 : :
575 : : void
720 576 : 203 : _PG_init(void)
577 : : {
578 [ + + ]: 203 : if (!process_shared_preload_libraries_in_progress)
579 : 200 : return;
580 : :
111 heikki.linnakangas@i 581 : 3 : RegisterShmemCallbacks(&injection_shmem_callbacks);
582 : : }
|