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-2024, 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"
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 
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 
96 extern PGDLLEXPORT void injection_error(const char *name,
97  const void *private_data);
98 extern PGDLLEXPORT void injection_notice(const char *name,
99  const void *private_data);
100 extern PGDLLEXPORT void injection_wait(const char *name,
101  const void *private_data);
102 
103 /* track if injection points attached in this process are linked to it */
104 static bool injection_point_local = false;
105 
106 /*
107  * GUC variable
108  *
109  * This GUC is useful to control if statistics should be enabled or not
110  * during a test with injection points, like for example if a test relies
111  * on a callback run in a critical section where no allocation should happen.
112  */
113 bool inj_stats_enabled = false;
114 
115 /* Shared memory init callbacks */
118 
119 /*
120  * Routine for shared memory area initialization, used as a callback
121  * when initializing dynamically with a DSM or when loading the module.
122  */
123 static void
125 {
127 
128  SpinLockInit(&state->lock);
129  memset(state->wait_counts, 0, sizeof(state->wait_counts));
130  memset(state->name, 0, sizeof(state->name));
131  ConditionVariableInit(&state->wait_point);
132 }
133 
134 /* Shared memory initialization when loading module */
135 static void
137 {
138  Size size;
139 
142 
145 }
146 
147 static void
149 {
150  bool found;
151 
154 
155  /* Create or attach to the shared memory state */
156  LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
157 
158  inj_state = ShmemInitStruct("injection_points",
160  &found);
161 
162  if (!found)
163  {
164  /*
165  * First time through, so initialize. This is shared with the dynamic
166  * initialization using a DSM.
167  */
169  }
170 
171  LWLockRelease(AddinShmemInitLock);
172 }
173 
174 /*
175  * Initialize shared memory area for this module through DSM.
176  */
177 static void
179 {
180  bool found;
181 
182  if (inj_state != NULL)
183  return;
184 
185  inj_state = GetNamedDSMSegment("injection_points",
188  &found);
189 }
190 
191 /*
192  * Check runtime conditions associated to an injection point.
193  *
194  * Returns true if the named injection point is allowed to run, and false
195  * otherwise.
196  */
197 static bool
199 {
200  bool result = true;
201 
202  switch (condition->type)
203  {
204  case INJ_CONDITION_PID:
205  if (MyProcPid != condition->pid)
206  result = false;
207  break;
209  break;
210  }
211 
212  return result;
213 }
214 
215 /*
216  * before_shmem_exit callback to remove injection points linked to a
217  * specific process.
218  */
219 static void
221 {
222  ListCell *lc;
223 
224  /* Leave if nothing is tracked locally */
226  return;
227 
228  /* Detach all the local points */
229  foreach(lc, inj_list_local)
230  {
231  char *name = strVal(lfirst(lc));
232 
233  (void) InjectionPointDetach(name);
234 
235  /* Remove stats entry */
237  }
238 }
239 
240 /* Set of callbacks available to be attached to an injection point. */
241 void
242 injection_error(const char *name, const void *private_data)
243 {
244  InjectionPointCondition *condition = (InjectionPointCondition *) private_data;
245 
246  if (!injection_point_allowed(condition))
247  return;
248 
250 
251  elog(ERROR, "error triggered for injection point %s", name);
252 }
253 
254 void
255 injection_notice(const char *name, const void *private_data)
256 {
257  InjectionPointCondition *condition = (InjectionPointCondition *) private_data;
258 
259  if (!injection_point_allowed(condition))
260  return;
261 
263 
264  elog(NOTICE, "notice triggered for injection point %s", name);
265 }
266 
267 /* Wait on a condition variable, awaken by injection_points_wakeup() */
268 void
269 injection_wait(const char *name, const void *private_data)
270 {
271  uint32 old_wait_counts = 0;
272  int index = -1;
273  uint32 injection_wait_event = 0;
274  InjectionPointCondition *condition = (InjectionPointCondition *) private_data;
275 
276  if (inj_state == NULL)
278 
279  if (!injection_point_allowed(condition))
280  return;
281 
283 
284  /*
285  * Use the injection point name for this custom wait event. Note that
286  * this custom wait event name is not released, but we don't care much for
287  * testing as this should be short-lived.
288  */
289  injection_wait_event = WaitEventInjectionPointNew(name);
290 
291  /*
292  * Find a free slot to wait for, and register this injection point's name.
293  */
295  for (int i = 0; i < INJ_MAX_WAIT; i++)
296  {
297  if (inj_state->name[i][0] == '\0')
298  {
299  index = i;
301  old_wait_counts = inj_state->wait_counts[i];
302  break;
303  }
304  }
306 
307  if (index < 0)
308  elog(ERROR, "could not find free slot for wait of injection point %s ",
309  name);
310 
311  /* And sleep.. */
313  for (;;)
314  {
315  uint32 new_wait_counts;
316 
318  new_wait_counts = inj_state->wait_counts[index];
320 
321  if (old_wait_counts != new_wait_counts)
322  break;
323  ConditionVariableSleep(&inj_state->wait_point, injection_wait_event);
324  }
326 
327  /* Remove this injection point from the waiters. */
329  inj_state->name[index][0] = '\0';
331 }
332 
333 /*
334  * SQL function for creating an injection point.
335  */
337 Datum
339 {
342  char *function;
343  InjectionPointCondition condition = {0};
344 
345  if (strcmp(action, "error") == 0)
346  function = "injection_error";
347  else if (strcmp(action, "notice") == 0)
348  function = "injection_notice";
349  else if (strcmp(action, "wait") == 0)
350  function = "injection_wait";
351  else
352  elog(ERROR, "incorrect action \"%s\" for injection point creation", action);
353 
355  {
356  condition.type = INJ_CONDITION_PID;
357  condition.pid = MyProcPid;
358  }
359 
360  pgstat_report_inj_fixed(1, 0, 0, 0, 0);
361  InjectionPointAttach(name, "injection_points", function, &condition,
362  sizeof(InjectionPointCondition));
363 
365  {
366  MemoryContext oldctx;
367 
368  /* Local injection point, so track it for automated cleanup */
371  MemoryContextSwitchTo(oldctx);
372  }
373 
374  /* Add entry for stats */
376 
377  PG_RETURN_VOID();
378 }
379 
380 /*
381  * SQL function for loading an injection point.
382  */
384 Datum
386 {
388 
389  if (inj_state == NULL)
391 
392  pgstat_report_inj_fixed(0, 0, 0, 0, 1);
394 
395  PG_RETURN_VOID();
396 }
397 
398 /*
399  * SQL function for triggering an injection point.
400  */
402 Datum
404 {
406 
407  pgstat_report_inj_fixed(0, 0, 1, 0, 0);
409 
410  PG_RETURN_VOID();
411 }
412 
413 /*
414  * SQL function for triggering an injection point from cache.
415  */
417 Datum
419 {
421 
422  pgstat_report_inj_fixed(0, 0, 0, 1, 0);
424 
425  PG_RETURN_VOID();
426 }
427 
428 /*
429  * SQL function for waking up an injection point waiting in injection_wait().
430  */
432 Datum
434 {
436  int index = -1;
437 
438  if (inj_state == NULL)
440 
441  /* First bump the wait counter for the injection point to wake up */
443  for (int i = 0; i < INJ_MAX_WAIT; i++)
444  {
445  if (strcmp(name, inj_state->name[i]) == 0)
446  {
447  index = i;
448  break;
449  }
450  }
451  if (index < 0)
452  {
454  elog(ERROR, "could not find injection point %s to wake up", name);
455  }
458 
459  /* And broadcast the change to the waiters */
461  PG_RETURN_VOID();
462 }
463 
464 /*
465  * injection_points_set_local
466  *
467  * Track if any injection point created in this process ought to run only
468  * in this process. Such injection points are detached automatically when
469  * this process exits. This is useful to make test suites concurrent-safe.
470  */
472 Datum
474 {
475  /* Enable flag to add a runtime condition based on this process ID */
476  injection_point_local = true;
477 
478  if (inj_state == NULL)
480 
481  /*
482  * Register a before_shmem_exit callback to remove any injection points
483  * linked to this process.
484  */
486 
487  PG_RETURN_VOID();
488 }
489 
490 /*
491  * SQL function for dropping an injection point.
492  */
494 Datum
496 {
498 
499  pgstat_report_inj_fixed(0, 1, 0, 0, 0);
501  elog(ERROR, "could not detach injection point \"%s\"", name);
502 
503  /* Remove point from local list, if required */
504  if (inj_list_local != NIL)
505  {
506  MemoryContext oldctx;
507 
510  MemoryContextSwitchTo(oldctx);
511  }
512 
513  /* Remove stats entry */
515 
516  PG_RETURN_VOID();
517 }
518 
519 
520 void
521 _PG_init(void)
522 {
524  return;
525 
526  DefineCustomBoolVariable("injection_points.stats",
527  "Enables statistics for injection points.",
528  NULL,
530  false,
532  0,
533  NULL,
534  NULL,
535  NULL);
536 
537  MarkGUCPrefixReserved("injection_points");
538 
539  /* Shared memory initialization */
544 
547 }
unsigned int uint32
Definition: c.h:509
#define MAXALIGN(LEN)
Definition: c.h:814
#define PGDLLEXPORT
Definition: c.h:1334
size_t Size
Definition: c.h:608
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_FUNCTION_ARGS
Definition: fmgr.h:193
int MyProcPid
Definition: globals.c:46
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:5091
void MarkGUCPrefixReserved(const char *className)
Definition: guc.c:5238
@ PGC_POSTMASTER
Definition: guc.h:70
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)
#define INJECTION_POINT_CACHED(name)
#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)
PGDLLEXPORT void injection_wait(const char *name, const void *private_data)
void _PG_init(void)
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)
PGDLLEXPORT void injection_notice(const char *name, const void *private_data)
static void injection_shmem_startup(void)
struct InjectionPointSharedState InjectionPointSharedState
Datum injection_points_wakeup(PG_FUNCTION_ARGS)
PGDLLEXPORT void injection_error(const char *name, const void *private_data)
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:60
void RequestAddinShmemSpace(Size size)
Definition: ipci.c:76
int i
Definition: isn.c:73
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:1168
void LWLockRelease(LWLock *lock)
Definition: lwlock.c:1781
@ LW_EXCLUSIVE
Definition: lwlock.h:114
char * pstrdup(const char *in)
Definition: mcxt.c:1696
MemoryContext TopMemoryContext
Definition: mcxt.c:149
void(* shmem_request_hook_type)(void)
Definition: miscadmin.h:507
shmem_request_hook_type shmem_request_hook
Definition: miscinit.c:1781
bool process_shared_preload_libraries_in_progress
Definition: miscinit.c:1778
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:64
MemoryContextSwitchTo(old_ctx)
void * ShmemInitStruct(const char *name, Size size, bool *foundPtr)
Definition: shmem.c:387
static pg_noinline void Size size
Definition: slab.c:607
#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:95
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:217
uint32 WaitEventInjectionPointNew(const char *wait_event_name)
Definition: wait_event.c:170
const char * name