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 "injection_points.h"
23#include "miscadmin.h"
24#include "nodes/pg_list.h"
25#include "nodes/value.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"
34#include "utils/memutils.h"
35#include "utils/tuplestore.h"
36#include "utils/wait_event.h"
37
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 */
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 */
61{
62 /* Protects access to other fields */
64
65 /* Counters advancing when injection_points_wakeup() is called */
67
68 /* Names of injection points attached to wait counters */
71
72/* Pointer to shared-memory state. */
74
75extern PGDLLEXPORT void injection_error(const char *name,
76 const void *private_data,
77 void *arg);
78extern PGDLLEXPORT void injection_notice(const char *name,
79 const void *private_data,
80 void *arg);
81extern 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 */
86static bool injection_point_local = false;
87
88static void injection_shmem_request(void *arg);
89static void injection_shmem_init(void *arg);
90
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 */
100static void
102{
104
105 SpinLockInit(&state->lock);
106 memset(state->name, 0, sizeof(state->name));
107 for (int i = 0; i < INJ_MAX_WAIT; i++)
108 pg_atomic_init_u32(&state->wait_counts[i], 0);
109}
110
111static void
113{
114 ShmemRequestStruct(.name = "injection_points",
115 .size = sizeof(InjectionPointSharedState),
116 .ptr = (void **) &inj_state,
117 );
118}
119
120static void
122{
123 /*
124 * First time through, so initialize. This is shared with the dynamic
125 * initialization using a DSM.
126 */
128}
129
130/*
131 * Initialize shared memory area for this module through DSM.
132 */
133static void
135{
136 bool found;
137
138 if (inj_state != NULL)
139 return;
140
141 inj_state = GetNamedDSMSegment("injection_points",
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 */
153static bool
155{
156 bool result = true;
157
158 switch (condition->type)
159 {
161 if (MyProcPid != condition->pid)
162 result = false;
163 break;
165 break;
166 }
167
168 return result;
169}
170
171/*
172 * before_shmem_exit callback to remove injection points linked to a
173 * specific process.
174 */
175static void
177{
178 ListCell *lc;
179
180 /* Leave if nothing is tracked locally */
182 return;
183
184 /* Detach all the local points */
185 foreach(lc, inj_list_local)
186 {
187 char *name = strVal(lfirst(lc));
188
190 }
191}
192
193/* Set of callbacks available to be attached to an injection point. */
194void
195injection_error(const char *name, const void *private_data, void *arg)
196{
197 const InjectionPointCondition *condition = private_data;
198 char *argstr = arg;
199
200 if (!injection_point_allowed(condition))
201 return;
202
203 if (argstr)
204 elog(ERROR, "error triggered for injection point %s (%s)",
205 name, argstr);
206 else
207 elog(ERROR, "error triggered for injection point %s", name);
208}
209
210void
211injection_notice(const char *name, const void *private_data, void *arg)
212{
213 const InjectionPointCondition *condition = private_data;
214 char *argstr = arg;
215
216 if (!injection_point_allowed(condition))
217 return;
218
219 if (argstr)
220 elog(NOTICE, "notice triggered for injection point %s (%s)",
221 name, argstr);
222 else
223 elog(NOTICE, "notice triggered for injection point %s", name);
224}
225
226/*
227 * Error cleanup callback for injection point waits.
228 */
229static void
238
239/* Wait until injection_points_wakeup() is called */
240void
241injection_wait(const char *name, const void *private_data, void *arg)
242{
244 int index = -1;
246 const InjectionPointCondition *condition = private_data;
247 int delay_us = 0;
248
249 if (inj_state == NULL)
251
252 if (!injection_point_allowed(condition))
253 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 */
261
262 /*
263 * Find a free slot to wait for, and register this injection point's name.
264 */
266 for (int i = 0; i < INJ_MAX_WAIT; i++)
267 {
268 if (inj_state->name[i][0] == '\0')
269 {
270 index = i;
273 break;
274 }
275 }
277
278 if (index < 0)
279 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 */
289
292 {
294 {
299 }
300 }
303
304 /* Remove this injection point from the waiters. */
306}
307
308/*
309 * SQL function for creating an injection point.
310 */
312Datum
314{
316 char *action = text_to_cstring(PG_GETARG_TEXT_PP(1));
317 char *function;
318 InjectionPointCondition condition = {0};
319
320 if (strcmp(action, "error") == 0)
321 function = "injection_error";
322 else if (strcmp(action, "notice") == 0)
323 function = "injection_notice";
324 else if (strcmp(action, "wait") == 0)
325 function = "injection_wait";
326 else
327 elog(ERROR, "incorrect action \"%s\" for injection point creation", action);
328
330 {
331 condition.type = INJ_CONDITION_PID;
332 condition.pid = MyProcPid;
333 }
334
335 InjectionPointAttach(name, "injection_points", function, &condition,
337
339 {
341
342 /* Local injection point, so track it for automated cleanup */
346 }
347
349}
350
351/*
352 * SQL function for creating an injection point with library name, function
353 * name and private data.
354 */
356Datum
358{
359 char *name;
360 char *lib_name;
361 char *function;
362 bytea *private_data = NULL;
363 int private_data_size = 0;
364
365 if (PG_ARGISNULL(0))
366 elog(ERROR, "injection point name cannot be NULL");
367 if (PG_ARGISNULL(1))
368 elog(ERROR, "injection point library cannot be NULL");
369 if (PG_ARGISNULL(2))
370 elog(ERROR, "injection point function cannot be NULL");
371
375
376 if (!PG_ARGISNULL(3))
377 {
378 private_data = PG_GETARG_BYTEA_PP(3);
379 private_data_size = VARSIZE_ANY_EXHDR(private_data);
380 }
381
382 if (private_data != NULL)
385 else
387 0);
389}
390
391/*
392 * SQL function for loading an injection point.
393 */
395Datum
407
408/*
409 * SQL function for triggering an injection point.
410 */
412Datum
414{
415 char *name;
416 char *arg = NULL;
417
418 if (PG_ARGISNULL(0))
421
422 if (!PG_ARGISNULL(1))
424
426
428}
429
430/*
431 * SQL function for triggering an injection point from cache.
432 */
434Datum
451
452/*
453 * SQL function for waking up an injection point waiting in injection_wait().
454 */
456Datum
458{
460 int index = -1;
461
462 if (inj_state == NULL)
464
465 /* Find the injection point then bump its wait counter */
467 for (int i = 0; i < INJ_MAX_WAIT; i++)
468 {
469 if (strcmp(name, inj_state->name[i]) == 0)
470 {
471 index = i;
472 break;
473 }
474 }
475 if (index < 0)
476 {
478 elog(ERROR, "could not find injection point %s to wake up", name);
479 }
481
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 */
494Datum
496{
497 /* Enable flag to add a runtime condition based on this process ID */
499
500 if (inj_state == NULL)
502
503 /*
504 * Register a before_shmem_exit callback to remove any injection points
505 * linked to this process.
506 */
508
510}
511
512/*
513 * SQL function for dropping an injection point.
514 */
516Datum
518{
520
522 elog(ERROR, "could not detach injection point \"%s\"", name);
523
524 /* Remove point from local list, if required */
525 if (inj_list_local != NIL)
526 {
528
532 }
533
535}
536
537/*
538 * SQL function for listing all the injection points attached.
539 */
541Datum
543{
544#define NUM_INJECTION_POINTS_LIST 3
545 ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
547 ListCell *lc;
548
549 /* Build a tuplestore to return our results in */
550 InitMaterializedSRF(fcinfo, 0);
551
553
554 foreach(lc, inj_points)
555 {
557 bool nulls[NUM_INJECTION_POINTS_LIST];
559
560 memset(values, 0, sizeof(values));
561 memset(nulls, 0, sizeof(nulls));
562
566
567 /* shove row into tuplestore */
568 tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
569 }
570
571 return (Datum) 0;
572#undef NUM_INJECTION_POINTS_LIST
573}
574
575void
static void pg_atomic_init_u32(volatile pg_atomic_uint32 *ptr, uint32 val)
Definition atomics.h:214
static uint32 pg_atomic_fetch_add_u32(volatile pg_atomic_uint32 *ptr, int32 add_)
Definition atomics.h:361
static uint32 pg_atomic_read_u32(volatile pg_atomic_uint32 *ptr)
Definition atomics.h:232
static Datum values[MAXATTR]
Definition bootstrap.c:190
#define Min(x, y)
Definition c.h:1131
#define PGDLLEXPORT
Definition c.h:1493
uint32_t uint32
Definition c.h:683
uint32 result
void * GetNamedDSMSegment(const char *name, size_t size, void(*init_callback)(void *ptr, void *arg), bool *found, void *arg)
Datum arg
Definition elog.c:1323
#define ERROR
Definition elog.h:40
#define elog(elevel,...)
Definition elog.h:228
#define NOTICE
Definition elog.h:36
#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, uint32 flags)
Definition funcapi.c:76
int MyProcPid
Definition globals.c:49
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
#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)
static void injection_init_shmem(void)
Datum injection_points_cached(PG_FUNCTION_ARGS)
PG_MODULE_MAGIC
#define INJ_WAIT_INITIAL_US
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 void injection_shmem_init(void *arg)
#define INJ_NAME_MAXLEN
static void injection_points_cleanup(int code, Datum arg)
static void injection_wait_cleanup(int code, Datum arg)
#define INJ_WAIT_MAX_US
PGDLLEXPORT void injection_error(const char *name, const void *private_data, void *arg)
static const ShmemCallbacks injection_shmem_callbacks
static void injection_shmem_request(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)
@ INJ_CONDITION_PID
@ INJ_CONDITION_ALWAYS
void before_shmem_exit(pg_on_exit_callback function, Datum arg)
Definition ipc.c:344
#define PG_ENSURE_ERROR_CLEANUP(cleanup_function, arg)
Definition ipc.h:47
#define PG_END_ENSURE_ERROR_CLEANUP(cleanup_function, arg)
Definition ipc.h:52
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
char * pstrdup(const char *in)
Definition mcxt.c:1910
MemoryContext TopMemoryContext
Definition mcxt.c:167
#define CHECK_FOR_INTERRUPTS()
Definition miscadmin.h:125
bool process_shared_preload_libraries_in_progress
Definition miscinit.c:1790
static MemoryContext MemoryContextSwitchTo(MemoryContext context)
Definition palloc.h:138
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
uint64_t Datum
Definition postgres.h:70
static Datum Int32GetDatum(int32 X)
Definition postgres.h:212
static int32 DatumGetInt32(Datum X)
Definition postgres.h:202
#define PointerGetDatum(X)
Definition postgres.h:354
static int fb(int x)
void RegisterShmemCallbacks(const ShmemCallbacks *callbacks)
Definition shmem.c:873
#define ShmemRequestStruct(...)
Definition shmem.h:176
void pg_usleep(long microsec)
Definition signal.c:53
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
pg_atomic_uint32 wait_counts[INJ_MAX_WAIT]
char name[INJ_MAX_WAIT][INJ_NAME_MAXLEN]
Definition pg_list.h:54
ShmemRequestCallback request_fn
Definition shmem.h:133
Definition type.h:97
Definition c.h:835
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:146
static void pgstat_report_wait_start(uint32 wait_event_info)
Definition wait_event.h:67
static void pgstat_report_wait_end(void)
Definition wait_event.h:83
const char * name