PostgreSQL Source Code git master
injection_points.c
Go to the documentation of this file.
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-2025, 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"
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"
33#include "utils/memutils.h"
34#include "utils/wait_event.h"
35
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 */
52{
53 INJ_CONDITION_ALWAYS = 0, /* always run */
54 INJ_CONDITION_PID, /* PID restriction */
56
58{
59 /* Type of the condition */
61
62 /* ID of the process where the injection point is allowed to run */
63 int pid;
65
66/*
67 * List of injection points stored in TopMemoryContext attached
68 * locally to this process.
69 */
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 */
79{
80 /* Protects access to other fields */
81 slock_t lock;
82
83 /* Counters advancing when injection_points_wakeup() is called */
85
86 /* Names of injection points attached to wait counters */
88
89 /* Condition variable used for waits and wakeups */
92
93/* Pointer to shared-memory state. */
95
96extern PGDLLEXPORT void injection_error(const char *name,
97 const void *private_data,
98 void *arg);
99extern PGDLLEXPORT void injection_notice(const char *name,
100 const void *private_data,
101 void *arg);
102extern 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 */
107static bool injection_point_local = false;
108
109/* Shared memory init callbacks */
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 */
117static void
119{
121
122 SpinLockInit(&state->lock);
123 memset(state->wait_counts, 0, sizeof(state->wait_counts));
124 memset(state->name, 0, sizeof(state->name));
125 ConditionVariableInit(&state->wait_point);
126}
127
128/* Shared memory initialization when loading module */
129static void
131{
132 Size size;
133
136
137 size = MAXALIGN(sizeof(InjectionPointSharedState));
139}
140
141static void
143{
144 bool found;
145
148
149 /* Create or attach to the shared memory state */
150 LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
151
152 inj_state = ShmemInitStruct("injection_points",
154 &found);
155
156 if (!found)
157 {
158 /*
159 * First time through, so initialize. This is shared with the dynamic
160 * initialization using a DSM.
161 */
163 }
164
165 LWLockRelease(AddinShmemInitLock);
166}
167
168/*
169 * Initialize shared memory area for this module through DSM.
170 */
171static void
173{
174 bool found;
175
176 if (inj_state != NULL)
177 return;
178
179 inj_state = GetNamedDSMSegment("injection_points",
182 &found);
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 */
191static bool
193{
194 bool result = true;
195
196 switch (condition->type)
197 {
199 if (MyProcPid != condition->pid)
200 result = false;
201 break;
203 break;
204 }
205
206 return result;
207}
208
209/*
210 * before_shmem_exit callback to remove injection points linked to a
211 * specific process.
212 */
213static void
215{
216 ListCell *lc;
217
218 /* Leave if nothing is tracked locally */
220 return;
221
222 /* Detach all the local points */
223 foreach(lc, inj_list_local)
224 {
225 char *name = strVal(lfirst(lc));
226
228 }
229}
230
231/* Set of callbacks available to be attached to an injection point. */
232void
233injection_error(const char *name, const void *private_data, void *arg)
234{
235 InjectionPointCondition *condition = (InjectionPointCondition *) private_data;
236 char *argstr = (char *) arg;
237
238 if (!injection_point_allowed(condition))
239 return;
240
241 if (argstr)
242 elog(ERROR, "error triggered for injection point %s (%s)",
243 name, argstr);
244 else
245 elog(ERROR, "error triggered for injection point %s", name);
246}
247
248void
249injection_notice(const char *name, const void *private_data, void *arg)
250{
251 InjectionPointCondition *condition = (InjectionPointCondition *) private_data;
252 char *argstr = (char *) arg;
253
254 if (!injection_point_allowed(condition))
255 return;
256
257 if (argstr)
258 elog(NOTICE, "notice triggered for injection point %s (%s)",
259 name, argstr);
260 else
261 elog(NOTICE, "notice triggered for injection point %s", name);
262}
263
264/* Wait on a condition variable, awaken by injection_points_wakeup() */
265void
266injection_wait(const char *name, const void *private_data, void *arg)
267{
268 uint32 old_wait_counts = 0;
269 int index = -1;
270 uint32 injection_wait_event = 0;
271 InjectionPointCondition *condition = (InjectionPointCondition *) private_data;
272
273 if (inj_state == NULL)
275
276 if (!injection_point_allowed(condition))
277 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 */
284 injection_wait_event = WaitEventInjectionPointNew(name);
285
286 /*
287 * Find a free slot to wait for, and register this injection point's name.
288 */
290 for (int i = 0; i < INJ_MAX_WAIT; i++)
291 {
292 if (inj_state->name[i][0] == '\0')
293 {
294 index = i;
296 old_wait_counts = inj_state->wait_counts[i];
297 break;
298 }
299 }
301
302 if (index < 0)
303 elog(ERROR, "could not find free slot for wait of injection point %s ",
304 name);
305
306 /* And sleep.. */
308 for (;;)
309 {
310 uint32 new_wait_counts;
311
313 new_wait_counts = inj_state->wait_counts[index];
315
316 if (old_wait_counts != new_wait_counts)
317 break;
318 ConditionVariableSleep(&inj_state->wait_point, injection_wait_event);
319 }
321
322 /* Remove this injection point from the waiters. */
324 inj_state->name[index][0] = '\0';
326}
327
328/*
329 * SQL function for creating an injection point.
330 */
332Datum
334{
337 char *function;
338 InjectionPointCondition condition = {0};
339
340 if (strcmp(action, "error") == 0)
341 function = "injection_error";
342 else if (strcmp(action, "notice") == 0)
343 function = "injection_notice";
344 else if (strcmp(action, "wait") == 0)
345 function = "injection_wait";
346 else
347 elog(ERROR, "incorrect action \"%s\" for injection point creation", action);
348
350 {
351 condition.type = INJ_CONDITION_PID;
352 condition.pid = MyProcPid;
353 }
354
355 InjectionPointAttach(name, "injection_points", function, &condition,
357
359 {
360 MemoryContext oldctx;
361
362 /* Local injection point, so track it for automated cleanup */
365 MemoryContextSwitchTo(oldctx);
366 }
367
369}
370
371/*
372 * SQL function for creating an injection point with library name, function
373 * name and private data.
374 */
376Datum
378{
379 char *name;
380 char *lib_name;
381 char *function;
382 bytea *private_data = NULL;
383 int private_data_size = 0;
384
385 if (PG_ARGISNULL(0))
386 elog(ERROR, "injection point name cannot be NULL");
387 if (PG_ARGISNULL(1))
388 elog(ERROR, "injection point library cannot be NULL");
389 if (PG_ARGISNULL(2))
390 elog(ERROR, "injection point function cannot be NULL");
391
393 lib_name = text_to_cstring(PG_GETARG_TEXT_PP(1));
395
396 if (!PG_ARGISNULL(3))
397 {
398 private_data = PG_GETARG_BYTEA_PP(3);
399 private_data_size = VARSIZE_ANY_EXHDR(private_data);
400 }
401
402 if (private_data != NULL)
403 InjectionPointAttach(name, lib_name, function, VARDATA_ANY(private_data),
404 private_data_size);
405 else
406 InjectionPointAttach(name, lib_name, function, NULL,
407 0);
409}
410
411/*
412 * SQL function for loading an injection point.
413 */
415Datum
417{
419
420 if (inj_state == NULL)
422
424
426}
427
428/*
429 * SQL function for triggering an injection point.
430 */
432Datum
434{
435 char *name;
436 char *arg = NULL;
437
438 if (PG_ARGISNULL(0))
441
442 if (!PG_ARGISNULL(1))
444
446
448}
449
450/*
451 * SQL function for triggering an injection point from cache.
452 */
454Datum
456{
457 char *name;
458 char *arg = NULL;
459
460 if (PG_ARGISNULL(0))
463
464 if (!PG_ARGISNULL(1))
466
468
470}
471
472/*
473 * SQL function for waking up an injection point waiting in injection_wait().
474 */
476Datum
478{
480 int index = -1;
481
482 if (inj_state == NULL)
484
485 /* First bump the wait counter for the injection point to wake up */
487 for (int i = 0; i < INJ_MAX_WAIT; i++)
488 {
489 if (strcmp(name, inj_state->name[i]) == 0)
490 {
491 index = i;
492 break;
493 }
494 }
495 if (index < 0)
496 {
498 elog(ERROR, "could not find injection point %s to wake up", name);
499 }
502
503 /* And broadcast the change to the waiters */
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 */
516Datum
518{
519 /* Enable flag to add a runtime condition based on this process ID */
521
522 if (inj_state == NULL)
524
525 /*
526 * Register a before_shmem_exit callback to remove any injection points
527 * linked to this process.
528 */
530
532}
533
534/*
535 * SQL function for dropping an injection point.
536 */
538Datum
540{
542
544 elog(ERROR, "could not detach injection point \"%s\"", name);
545
546 /* Remove point from local list, if required */
547 if (inj_list_local != NIL)
548 {
549 MemoryContext oldctx;
550
553 MemoryContextSwitchTo(oldctx);
554 }
555
557}
558
559/*
560 * SQL function for listing all the injection points attached.
561 */
563Datum
565{
566#define NUM_INJECTION_POINTS_LIST 3
567 ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
568 List *inj_points;
569 ListCell *lc;
570
571 /* Build a tuplestore to return our results in */
572 InitMaterializedSRF(fcinfo, 0);
573
574 inj_points = InjectionPointList();
575
576 foreach(lc, inj_points)
577 {
579 bool nulls[NUM_INJECTION_POINTS_LIST];
580 InjectionPointData *inj_point = lfirst(lc);
581
582 memset(values, 0, sizeof(values));
583 memset(nulls, 0, sizeof(nulls));
584
585 values[0] = PointerGetDatum(cstring_to_text(inj_point->name));
588
589 /* shove row into tuplestore */
590 tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
591 }
592
593 return (Datum) 0;
594#undef NUM_INJECTION_POINTS_LIST
595}
596
597void
599{
601 return;
602
603 /* Shared memory initialization */
608}
static Datum values[MAXATTR]
Definition: bootstrap.c:153
#define MAXALIGN(LEN)
Definition: c.h:824
#define PGDLLEXPORT
Definition: c.h:1333
uint32_t uint32
Definition: c.h:552
size_t Size
Definition: c.h:624
bool ConditionVariableCancelSleep(void)
void ConditionVariableBroadcast(ConditionVariable *cv)
void ConditionVariablePrepareToSleep(ConditionVariable *cv)
void ConditionVariableInit(ConditionVariable *cv)
void ConditionVariableSleep(ConditionVariable *cv, uint32 wait_event_info)
void * GetNamedDSMSegment(const char *name, size_t size, void(*init_callback)(void *ptr), bool *found)
Definition: dsm_registry.c:186
#define ERROR
Definition: elog.h:39
#define elog(elevel,...)
Definition: elog.h:226
#define NOTICE
Definition: elog.h:35
#define PG_RETURN_VOID()
Definition: fmgr.h:349
#define PG_GETARG_BYTEA_PP(n)
Definition: fmgr.h:308
#define PG_GETARG_TEXT_PP(n)
Definition: fmgr.h:309
#define PG_ARGISNULL(n)
Definition: fmgr.h:209
#define PG_FUNCTION_ARGS
Definition: fmgr.h:193
void InitMaterializedSRF(FunctionCallInfo fcinfo, bits32 flags)
Definition: funcapi.c:76
int MyProcPid
Definition: globals.c:47
bool InjectionPointDetach(const char *name)
List * InjectionPointList(void)
void InjectionPointAttach(const char *name, const char *library, const char *function, const void *private_data, int private_data_size)
#define INJECTION_POINT(name, arg)
#define INJECTION_POINT_CACHED(name, arg)
#define INJECTION_POINT_LOAD(name)
Datum injection_points_detach(PG_FUNCTION_ARGS)
static bool injection_point_local
static void injection_shmem_request(void)
#define INJ_MAX_WAIT
PG_FUNCTION_INFO_V1(injection_points_attach)
void _PG_init(void)
PGDLLEXPORT void injection_wait(const char *name, const void *private_data, void *arg)
InjectionPointConditionType
@ INJ_CONDITION_PID
@ INJ_CONDITION_ALWAYS
static void injection_init_shmem(void)
Datum injection_points_cached(PG_FUNCTION_ARGS)
PG_MODULE_MAGIC
Datum injection_points_attach(PG_FUNCTION_ARGS)
struct InjectionPointCondition InjectionPointCondition
Datum injection_points_run(PG_FUNCTION_ARGS)
static List * inj_list_local
Datum injection_points_set_local(PG_FUNCTION_ARGS)
static shmem_startup_hook_type prev_shmem_startup_hook
static bool injection_point_allowed(InjectionPointCondition *condition)
static shmem_request_hook_type prev_shmem_request_hook
#define INJ_NAME_MAXLEN
static void injection_points_cleanup(int code, Datum arg)
static void injection_shmem_startup(void)
PGDLLEXPORT void injection_error(const char *name, const void *private_data, void *arg)
struct InjectionPointSharedState InjectionPointSharedState
Datum injection_points_attach_func(PG_FUNCTION_ARGS)
#define NUM_INJECTION_POINTS_LIST
Datum injection_points_wakeup(PG_FUNCTION_ARGS)
PGDLLEXPORT void injection_notice(const char *name, const void *private_data, void *arg)
Datum injection_points_list(PG_FUNCTION_ARGS)
static void injection_point_init_state(void *ptr)
static InjectionPointSharedState * inj_state
Datum injection_points_load(PG_FUNCTION_ARGS)
void before_shmem_exit(pg_on_exit_callback function, Datum arg)
Definition: ipc.c:337
void(* shmem_startup_hook_type)(void)
Definition: ipc.h:22
shmem_startup_hook_type shmem_startup_hook
Definition: ipci.c:59
void RequestAddinShmemSpace(Size size)
Definition: ipci.c:75
int i
Definition: isn.c:77
List * lappend(List *list, void *datum)
Definition: list.c:339
List * list_delete(List *list, void *datum)
Definition: list.c:853
bool LWLockAcquire(LWLock *lock, LWLockMode mode)
Definition: lwlock.c:1174
void LWLockRelease(LWLock *lock)
Definition: lwlock.c:1894
@ LW_EXCLUSIVE
Definition: lwlock.h:112
char * pstrdup(const char *in)
Definition: mcxt.c:1759
MemoryContext TopMemoryContext
Definition: mcxt.c:166
void(* shmem_request_hook_type)(void)
Definition: miscadmin.h:533
shmem_request_hook_type shmem_request_hook
Definition: miscinit.c:1789
bool process_shared_preload_libraries_in_progress
Definition: miscinit.c:1786
static MemoryContext MemoryContextSwitchTo(MemoryContext context)
Definition: palloc.h:124
on_exit_nicely_callback function
void * arg
#define lfirst(lc)
Definition: pg_list.h:172
#define NIL
Definition: pg_list.h:68
size_t strlcpy(char *dst, const char *src, size_t siz)
Definition: strlcpy.c:45
static Datum PointerGetDatum(const void *X)
Definition: postgres.h:332
uint64_t Datum
Definition: postgres.h:70
void * ShmemInitStruct(const char *name, Size size, bool *foundPtr)
Definition: shmem.c:389
#define SpinLockInit(lock)
Definition: spin.h:57
#define SpinLockRelease(lock)
Definition: spin.h:61
#define SpinLockAcquire(lock)
Definition: spin.h:59
InjectionPointConditionType type
const char * library
const char * function
uint32 wait_counts[INJ_MAX_WAIT]
char name[INJ_MAX_WAIT][INJ_NAME_MAXLEN]
ConditionVariable wait_point
Definition: pg_list.h:54
TupleDesc setDesc
Definition: execnodes.h:364
Tuplestorestate * setResult
Definition: execnodes.h:363
Definition: type.h:96
Definition: regguts.h:323
Definition: c.h:706
void tuplestore_putvalues(Tuplestorestate *state, TupleDesc tdesc, const Datum *values, const bool *isnull)
Definition: tuplestore.c:784
String * makeString(char *str)
Definition: value.c:63
#define strVal(v)
Definition: value.h:82
static Size VARSIZE_ANY_EXHDR(const void *PTR)
Definition: varatt.h:472
static char * VARDATA_ANY(const void *PTR)
Definition: varatt.h:486
text * cstring_to_text(const char *s)
Definition: varlena.c:181
char * text_to_cstring(const text *t)
Definition: varlena.c:214
uint32 WaitEventInjectionPointNew(const char *wait_event_name)
Definition: wait_event.c:169
const char * name