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