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 "injection_stats.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/*
110 * GUC variable
111 *
112 * This GUC is useful to control if statistics should be enabled or not
113 * during a test with injection points, like for example if a test relies
114 * on a callback run in a critical section where no allocation should happen.
115 */
116bool inj_stats_enabled = false;
117
118/* Shared memory init callbacks */
121
122/*
123 * Routine for shared memory area initialization, used as a callback
124 * when initializing dynamically with a DSM or when loading the module.
125 */
126static void
128{
130
131 SpinLockInit(&state->lock);
132 memset(state->wait_counts, 0, sizeof(state->wait_counts));
133 memset(state->name, 0, sizeof(state->name));
134 ConditionVariableInit(&state->wait_point);
135}
136
137/* Shared memory initialization when loading module */
138static void
140{
141 Size size;
142
145
146 size = MAXALIGN(sizeof(InjectionPointSharedState));
148}
149
150static void
152{
153 bool found;
154
157
158 /* Create or attach to the shared memory state */
159 LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
160
161 inj_state = ShmemInitStruct("injection_points",
163 &found);
164
165 if (!found)
166 {
167 /*
168 * First time through, so initialize. This is shared with the dynamic
169 * initialization using a DSM.
170 */
172 }
173
174 LWLockRelease(AddinShmemInitLock);
175}
176
177/*
178 * Initialize shared memory area for this module through DSM.
179 */
180static void
182{
183 bool found;
184
185 if (inj_state != NULL)
186 return;
187
188 inj_state = GetNamedDSMSegment("injection_points",
191 &found);
192}
193
194/*
195 * Check runtime conditions associated to an injection point.
196 *
197 * Returns true if the named injection point is allowed to run, and false
198 * otherwise.
199 */
200static bool
202{
203 bool result = true;
204
205 switch (condition->type)
206 {
208 if (MyProcPid != condition->pid)
209 result = false;
210 break;
212 break;
213 }
214
215 return result;
216}
217
218/*
219 * before_shmem_exit callback to remove injection points linked to a
220 * specific process.
221 */
222static void
224{
225 ListCell *lc;
226
227 /* Leave if nothing is tracked locally */
229 return;
230
231 /* Detach all the local points */
232 foreach(lc, inj_list_local)
233 {
234 char *name = strVal(lfirst(lc));
235
237
238 /* Remove stats entry */
240 }
241}
242
243/* Set of callbacks available to be attached to an injection point. */
244void
245injection_error(const char *name, const void *private_data, void *arg)
246{
247 InjectionPointCondition *condition = (InjectionPointCondition *) private_data;
248 char *argstr = (char *) arg;
249
250 if (!injection_point_allowed(condition))
251 return;
252
254
255 if (argstr)
256 elog(ERROR, "error triggered for injection point %s (%s)",
257 name, argstr);
258 else
259 elog(ERROR, "error triggered for injection point %s", name);
260}
261
262void
263injection_notice(const char *name, const void *private_data, void *arg)
264{
265 InjectionPointCondition *condition = (InjectionPointCondition *) private_data;
266 char *argstr = (char *) arg;
267
268 if (!injection_point_allowed(condition))
269 return;
270
272
273 if (argstr)
274 elog(NOTICE, "notice triggered for injection point %s (%s)",
275 name, argstr);
276 else
277 elog(NOTICE, "notice triggered for injection point %s", name);
278}
279
280/* Wait on a condition variable, awaken by injection_points_wakeup() */
281void
282injection_wait(const char *name, const void *private_data, void *arg)
283{
284 uint32 old_wait_counts = 0;
285 int index = -1;
286 uint32 injection_wait_event = 0;
287 InjectionPointCondition *condition = (InjectionPointCondition *) private_data;
288
289 if (inj_state == NULL)
291
292 if (!injection_point_allowed(condition))
293 return;
294
296
297 /*
298 * Use the injection point name for this custom wait event. Note that
299 * this custom wait event name is not released, but we don't care much for
300 * testing as this should be short-lived.
301 */
302 injection_wait_event = WaitEventInjectionPointNew(name);
303
304 /*
305 * Find a free slot to wait for, and register this injection point's name.
306 */
308 for (int i = 0; i < INJ_MAX_WAIT; i++)
309 {
310 if (inj_state->name[i][0] == '\0')
311 {
312 index = i;
314 old_wait_counts = inj_state->wait_counts[i];
315 break;
316 }
317 }
319
320 if (index < 0)
321 elog(ERROR, "could not find free slot for wait of injection point %s ",
322 name);
323
324 /* And sleep.. */
326 for (;;)
327 {
328 uint32 new_wait_counts;
329
331 new_wait_counts = inj_state->wait_counts[index];
333
334 if (old_wait_counts != new_wait_counts)
335 break;
336 ConditionVariableSleep(&inj_state->wait_point, injection_wait_event);
337 }
339
340 /* Remove this injection point from the waiters. */
342 inj_state->name[index][0] = '\0';
344}
345
346/*
347 * SQL function for creating an injection point.
348 */
350Datum
352{
355 char *function;
356 InjectionPointCondition condition = {0};
357
358 if (strcmp(action, "error") == 0)
359 function = "injection_error";
360 else if (strcmp(action, "notice") == 0)
361 function = "injection_notice";
362 else if (strcmp(action, "wait") == 0)
363 function = "injection_wait";
364 else
365 elog(ERROR, "incorrect action \"%s\" for injection point creation", action);
366
368 {
369 condition.type = INJ_CONDITION_PID;
370 condition.pid = MyProcPid;
371 }
372
373 pgstat_report_inj_fixed(1, 0, 0, 0, 0);
374 InjectionPointAttach(name, "injection_points", function, &condition,
376
378 {
379 MemoryContext oldctx;
380
381 /* Local injection point, so track it for automated cleanup */
384 MemoryContextSwitchTo(oldctx);
385 }
386
387 /* Add entry for stats */
389
391}
392
393/*
394 * SQL function for loading an injection point.
395 */
397Datum
399{
401
402 if (inj_state == NULL)
404
405 pgstat_report_inj_fixed(0, 0, 0, 0, 1);
407
409}
410
411/*
412 * SQL function for triggering an injection point.
413 */
415Datum
417{
418 char *name;
419 char *arg = NULL;
420
421 if (PG_ARGISNULL(0))
424
425 if (!PG_ARGISNULL(1))
427
428 pgstat_report_inj_fixed(0, 0, 1, 0, 0);
430
432}
433
434/*
435 * SQL function for triggering an injection point from cache.
436 */
438Datum
440{
441 char *name;
442 char *arg = NULL;
443
444 if (PG_ARGISNULL(0))
447
448 if (!PG_ARGISNULL(1))
450
451 pgstat_report_inj_fixed(0, 0, 0, 1, 0);
453
455}
456
457/*
458 * SQL function for waking up an injection point waiting in injection_wait().
459 */
461Datum
463{
465 int index = -1;
466
467 if (inj_state == NULL)
469
470 /* First bump the wait counter for the injection point to wake up */
472 for (int i = 0; i < INJ_MAX_WAIT; i++)
473 {
474 if (strcmp(name, inj_state->name[i]) == 0)
475 {
476 index = i;
477 break;
478 }
479 }
480 if (index < 0)
481 {
483 elog(ERROR, "could not find injection point %s to wake up", name);
484 }
487
488 /* And broadcast the change to the waiters */
491}
492
493/*
494 * injection_points_set_local
495 *
496 * Track if any injection point created in this process ought to run only
497 * in this process. Such injection points are detached automatically when
498 * this process exits. This is useful to make test suites concurrent-safe.
499 */
501Datum
503{
504 /* Enable flag to add a runtime condition based on this process ID */
506
507 if (inj_state == NULL)
509
510 /*
511 * Register a before_shmem_exit callback to remove any injection points
512 * linked to this process.
513 */
515
517}
518
519/*
520 * SQL function for dropping an injection point.
521 */
523Datum
525{
527
528 pgstat_report_inj_fixed(0, 1, 0, 0, 0);
530 elog(ERROR, "could not detach injection point \"%s\"", name);
531
532 /* Remove point from local list, if required */
533 if (inj_list_local != NIL)
534 {
535 MemoryContext oldctx;
536
539 MemoryContextSwitchTo(oldctx);
540 }
541
542 /* Remove stats entry */
544
546}
547
548
549void
551{
553 return;
554
555 DefineCustomBoolVariable("injection_points.stats",
556 "Enables statistics for injection points.",
557 NULL,
559 false,
561 0,
562 NULL,
563 NULL,
564 NULL);
565
566 MarkGUCPrefixReserved("injection_points");
567
568 /* Shared memory initialization */
573
576}
#define MAXALIGN(LEN)
Definition: c.h:782
#define PGDLLEXPORT
Definition: c.h:1306
uint32_t uint32
Definition: c.h:502
size_t Size
Definition: c.h:576
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:131
#define ERROR
Definition: elog.h:39
#define elog(elevel,...)
Definition: elog.h:225
#define NOTICE
Definition: elog.h:35
#define PG_RETURN_VOID()
Definition: fmgr.h:349
#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
int MyProcPid
Definition: globals.c:48
void DefineCustomBoolVariable(const char *name, const char *short_desc, const char *long_desc, bool *valueAddr, bool bootValue, GucContext context, int flags, GucBoolCheckHook check_hook, GucBoolAssignHook assign_hook, GucShowHook show_hook)
Definition: guc.c:5133
void MarkGUCPrefixReserved(const char *className)
Definition: guc.c:5280
@ PGC_POSTMASTER
Definition: guc.h:74
bool InjectionPointDetach(const char *name)
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)
bool inj_stats_enabled
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_wakeup(PG_FUNCTION_ARGS)
PGDLLEXPORT void injection_notice(const char *name, const void *private_data, void *arg)
static void injection_point_init_state(void *ptr)
static InjectionPointSharedState * inj_state
Datum injection_points_load(PG_FUNCTION_ARGS)
void pgstat_report_inj(const char *name)
void pgstat_register_inj(void)
void pgstat_create_inj(const char *name)
void pgstat_drop_inj(const char *name)
void pgstat_register_inj_fixed(void)
void pgstat_report_inj_fixed(uint32 numattach, uint32 numdetach, uint32 numrun, uint32 numcached, uint32 numloaded)
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:1182
void LWLockRelease(LWLock *lock)
Definition: lwlock.c:1902
@ LW_EXCLUSIVE
Definition: lwlock.h:114
char * pstrdup(const char *in)
Definition: mcxt.c:2327
MemoryContext TopMemoryContext
Definition: mcxt.c:165
void(* shmem_request_hook_type)(void)
Definition: miscadmin.h:533
shmem_request_hook_type shmem_request_hook
Definition: miscinit.c:1840
bool process_shared_preload_libraries_in_progress
Definition: miscinit.c:1837
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
uintptr_t Datum
Definition: postgres.h:69
void * ShmemInitStruct(const char *name, Size size, bool *foundPtr)
Definition: shmem.c:387
#define SpinLockInit(lock)
Definition: spin.h:57
#define SpinLockRelease(lock)
Definition: spin.h:61
#define SpinLockAcquire(lock)
Definition: spin.h:59
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: regguts.h:323
String * makeString(char *str)
Definition: value.c:63
#define strVal(v)
Definition: value.h:82
char * text_to_cstring(const text *t)
Definition: varlena.c:225
uint32 WaitEventInjectionPointNew(const char *wait_event_name)
Definition: wait_event.c:169
const char * name