PostgreSQL Source Code git master
Loading...
Searching...
No Matches
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-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"
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/tuplestore.h"
35#include "utils/wait_event.h"
36
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 */
57
59{
60 /* Type of the condition */
62
63 /* ID of the process where the injection point is allowed to run */
64 int pid;
66
67/*
68 * List of injection points stored in TopMemoryContext attached
69 * locally to this process.
70 */
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 */
80{
81 /* Protects access to other fields */
83
84 /* Counters advancing when injection_points_wakeup() is called */
86
87 /* Names of injection points attached to wait counters */
89
90 /* Condition variable used for waits and wakeups */
93
94/* Pointer to shared-memory state. */
96
97extern PGDLLEXPORT void injection_error(const char *name,
98 const void *private_data,
99 void *arg);
100extern PGDLLEXPORT void injection_notice(const char *name,
101 const void *private_data,
102 void *arg);
103extern 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 */
108static bool injection_point_local = false;
109
110/* Shared memory init callbacks */
113
114/*
115 * Routine for shared memory area initialization, used as a callback
116 * when initializing dynamically with a DSM or when loading the module.
117 */
118static void
120{
122
123 SpinLockInit(&state->lock);
124 memset(state->wait_counts, 0, sizeof(state->wait_counts));
125 memset(state->name, 0, sizeof(state->name));
126 ConditionVariableInit(&state->wait_point);
127}
128
129/* Shared memory initialization when loading module */
130static void
132{
133 Size size;
134
137
138 size = MAXALIGN(sizeof(InjectionPointSharedState));
140}
141
142static void
144{
145 bool found;
146
149
150 /* Create or attach to the shared memory state */
152
153 inj_state = ShmemInitStruct("injection_points",
155 &found);
156
157 if (!found)
158 {
159 /*
160 * First time through, so initialize. This is shared with the dynamic
161 * initialization using a DSM.
162 */
164 }
165
167}
168
169/*
170 * Initialize shared memory area for this module through DSM.
171 */
172static void
174{
175 bool found;
176
177 if (inj_state != NULL)
178 return;
179
180 inj_state = GetNamedDSMSegment("injection_points",
183 &found, NULL);
184}
185
186/*
187 * Check runtime conditions associated to an injection point.
188 *
189 * Returns true if the named injection point is allowed to run, and false
190 * otherwise.
191 */
192static bool
194{
195 bool result = true;
196
197 switch (condition->type)
198 {
200 if (MyProcPid != condition->pid)
201 result = false;
202 break;
204 break;
205 }
206
207 return result;
208}
209
210/*
211 * before_shmem_exit callback to remove injection points linked to a
212 * specific process.
213 */
214static void
216{
217 ListCell *lc;
218
219 /* Leave if nothing is tracked locally */
221 return;
222
223 /* Detach all the local points */
224 foreach(lc, inj_list_local)
225 {
226 char *name = strVal(lfirst(lc));
227
229 }
230}
231
232/* Set of callbacks available to be attached to an injection point. */
233void
234injection_error(const char *name, const void *private_data, void *arg)
235{
236 const InjectionPointCondition *condition = private_data;
237 char *argstr = arg;
238
239 if (!injection_point_allowed(condition))
240 return;
241
242 if (argstr)
243 elog(ERROR, "error triggered for injection point %s (%s)",
244 name, argstr);
245 else
246 elog(ERROR, "error triggered for injection point %s", name);
247}
248
249void
250injection_notice(const char *name, const void *private_data, void *arg)
251{
252 const InjectionPointCondition *condition = private_data;
253 char *argstr = arg;
254
255 if (!injection_point_allowed(condition))
256 return;
257
258 if (argstr)
259 elog(NOTICE, "notice triggered for injection point %s (%s)",
260 name, argstr);
261 else
262 elog(NOTICE, "notice triggered for injection point %s", name);
263}
264
265/* Wait on a condition variable, awaken by injection_points_wakeup() */
266void
267injection_wait(const char *name, const void *private_data, void *arg)
268{
270 int index = -1;
272 const InjectionPointCondition *condition = private_data;
273
274 if (inj_state == NULL)
276
277 if (!injection_point_allowed(condition))
278 return;
279
280 /*
281 * Use the injection point name for this custom wait event. Note that
282 * this custom wait event name is not released, but we don't care much for
283 * testing as this should be short-lived.
284 */
286
287 /*
288 * Find a free slot to wait for, and register this injection point's name.
289 */
291 for (int i = 0; i < INJ_MAX_WAIT; i++)
292 {
293 if (inj_state->name[i][0] == '\0')
294 {
295 index = i;
298 break;
299 }
300 }
302
303 if (index < 0)
304 elog(ERROR, "could not find free slot for wait of injection point %s ",
305 name);
306
307 /* And sleep.. */
309 for (;;)
310 {
312
316
318 break;
320 }
322
323 /* Remove this injection point from the waiters. */
325 inj_state->name[index][0] = '\0';
327}
328
329/*
330 * SQL function for creating an injection point.
331 */
333Datum
335{
337 char *action = text_to_cstring(PG_GETARG_TEXT_PP(1));
338 char *function;
339 InjectionPointCondition condition = {0};
340
341 if (strcmp(action, "error") == 0)
342 function = "injection_error";
343 else if (strcmp(action, "notice") == 0)
344 function = "injection_notice";
345 else if (strcmp(action, "wait") == 0)
346 function = "injection_wait";
347 else
348 elog(ERROR, "incorrect action \"%s\" for injection point creation", action);
349
351 {
352 condition.type = INJ_CONDITION_PID;
353 condition.pid = MyProcPid;
354 }
355
356 InjectionPointAttach(name, "injection_points", function, &condition,
358
360 {
362
363 /* Local injection point, so track it for automated cleanup */
367 }
368
370}
371
372/*
373 * SQL function for creating an injection point with library name, function
374 * name and private data.
375 */
377Datum
379{
380 char *name;
381 char *lib_name;
382 char *function;
383 bytea *private_data = NULL;
384 int private_data_size = 0;
385
386 if (PG_ARGISNULL(0))
387 elog(ERROR, "injection point name cannot be NULL");
388 if (PG_ARGISNULL(1))
389 elog(ERROR, "injection point library cannot be NULL");
390 if (PG_ARGISNULL(2))
391 elog(ERROR, "injection point function cannot be NULL");
392
396
397 if (!PG_ARGISNULL(3))
398 {
399 private_data = PG_GETARG_BYTEA_PP(3);
400 private_data_size = VARSIZE_ANY_EXHDR(private_data);
401 }
402
403 if (private_data != NULL)
406 else
408 0);
410}
411
412/*
413 * SQL function for loading an injection point.
414 */
416Datum
428
429/*
430 * SQL function for triggering an injection point.
431 */
433Datum
435{
436 char *name;
437 char *arg = NULL;
438
439 if (PG_ARGISNULL(0))
442
443 if (!PG_ARGISNULL(1))
445
447
449}
450
451/*
452 * SQL function for triggering an injection point from cache.
453 */
455Datum
472
473/*
474 * SQL function for waking up an injection point waiting in injection_wait().
475 */
477Datum
479{
481 int index = -1;
482
483 if (inj_state == NULL)
485
486 /* First bump the wait counter for the injection point to wake up */
488 for (int i = 0; i < INJ_MAX_WAIT; i++)
489 {
490 if (strcmp(name, inj_state->name[i]) == 0)
491 {
492 index = i;
493 break;
494 }
495 }
496 if (index < 0)
497 {
499 elog(ERROR, "could not find injection point %s to wake up", name);
500 }
503
504 /* And broadcast the change to the waiters */
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 */
517Datum
519{
520 /* Enable flag to add a runtime condition based on this process ID */
522
523 if (inj_state == NULL)
525
526 /*
527 * Register a before_shmem_exit callback to remove any injection points
528 * linked to this process.
529 */
531
533}
534
535/*
536 * SQL function for dropping an injection point.
537 */
539Datum
541{
543
545 elog(ERROR, "could not detach injection point \"%s\"", name);
546
547 /* Remove point from local list, if required */
548 if (inj_list_local != NIL)
549 {
551
555 }
556
558}
559
560/*
561 * SQL function for listing all the injection points attached.
562 */
564Datum
566{
567#define NUM_INJECTION_POINTS_LIST 3
568 ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
570 ListCell *lc;
571
572 /* Build a tuplestore to return our results in */
573 InitMaterializedSRF(fcinfo, 0);
574
576
577 foreach(lc, inj_points)
578 {
580 bool nulls[NUM_INJECTION_POINTS_LIST];
582
583 memset(values, 0, sizeof(values));
584 memset(nulls, 0, sizeof(nulls));
585
589
590 /* shove row into tuplestore */
591 tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
592 }
593
594 return (Datum) 0;
595#undef NUM_INJECTION_POINTS_LIST
596}
597
598void
static Datum values[MAXATTR]
Definition bootstrap.c:188
#define MAXALIGN(LEN)
Definition c.h:898
#define PGDLLEXPORT
Definition c.h:1438
uint32_t uint32
Definition c.h:618
size_t Size
Definition c.h:691
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, void *arg), bool *found, void *arg)
Datum arg
Definition elog.c:1322
#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:350
#define PG_GETARG_BYTEA_PP(n)
Definition fmgr.h:309
#define PG_GETARG_TEXT_PP(n)
Definition fmgr.h:310
#define PG_ARGISNULL(n)
Definition fmgr.h:209
#define PG_FUNCTION_INFO_V1(funcname)
Definition fmgr.h:417
#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
static void injection_point_init_state(void *ptr, void *arg)
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)
Datum injection_points_run(PG_FUNCTION_ARGS)
static List * inj_list_local
static bool injection_point_allowed(const InjectionPointCondition *condition)
Datum injection_points_set_local(PG_FUNCTION_ARGS)
static shmem_startup_hook_type prev_shmem_startup_hook
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)
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 InjectionPointSharedState * inj_state
Datum injection_points_load(PG_FUNCTION_ARGS)
void before_shmem_exit(pg_on_exit_callback function, Datum arg)
Definition ipc.c:344
void(* shmem_startup_hook_type)(void)
Definition ipc.h:22
shmem_startup_hook_type shmem_startup_hook
Definition ipci.c:60
void RequestAddinShmemSpace(Size size)
Definition ipci.c:76
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:1177
void LWLockRelease(LWLock *lock)
Definition lwlock.c:1794
@ LW_EXCLUSIVE
Definition lwlock.h:112
char * pstrdup(const char *in)
Definition mcxt.c:1781
MemoryContext TopMemoryContext
Definition mcxt.c:166
void(* shmem_request_hook_type)(void)
Definition miscadmin.h:534
shmem_request_hook_type shmem_request_hook
Definition miscinit.c:1790
bool process_shared_preload_libraries_in_progress
Definition miscinit.c:1787
static MemoryContext MemoryContextSwitchTo(MemoryContext context)
Definition palloc.h:124
on_exit_nicely_callback function
#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:342
uint64_t Datum
Definition postgres.h:70
static int fb(int x)
void * ShmemInitStruct(const char *name, Size size, bool *foundPtr)
Definition shmem.c:381
static void SpinLockRelease(volatile slock_t *lock)
Definition spin.h:62
static void SpinLockAcquire(volatile slock_t *lock)
Definition spin.h:56
static void SpinLockInit(volatile slock_t *lock)
Definition spin.h:50
InjectionPointConditionType type
uint32 wait_counts[INJ_MAX_WAIT]
char name[INJ_MAX_WAIT][INJ_NAME_MAXLEN]
ConditionVariable wait_point
Definition pg_list.h:54
Definition type.h:96
Definition c.h:778
void tuplestore_putvalues(Tuplestorestate *state, TupleDesc tdesc, const Datum *values, const bool *isnull)
Definition tuplestore.c:785
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:184
char * text_to_cstring(const text *t)
Definition varlena.c:217
uint32 WaitEventInjectionPointNew(const char *wait_event_name)
Definition wait_event.c:169
const char * name