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 "miscadmin.h"
22 #include "nodes/pg_list.h"
23 #include "nodes/value.h"
25 #include "storage/dsm_registry.h"
26 #include "storage/ipc.h"
27 #include "storage/lwlock.h"
28 #include "storage/shmem.h"
29 #include "utils/builtins.h"
30 #include "utils/injection_point.h"
31 #include "utils/memutils.h"
32 #include "utils/wait_event.h"
33 
35 
36 /* Maximum number of waits usable in injection points at once */
37 #define INJ_MAX_WAIT 8
38 #define INJ_NAME_MAXLEN 64
39 
40 /*
41  * Conditions related to injection points. This tracks in shared memory the
42  * runtime conditions under which an injection point is allowed to run,
43  * stored as private_data when an injection point is attached, and passed as
44  * argument to the callback.
45  *
46  * If more types of runtime conditions need to be tracked, this structure
47  * should be expanded.
48  */
50 {
51  INJ_CONDITION_ALWAYS = 0, /* always run */
52  INJ_CONDITION_PID, /* PID restriction */
54 
56 {
57  /* Type of the condition */
59 
60  /* ID of the process where the injection point is allowed to run */
61  int pid;
63 
64 /*
65  * List of injection points stored in TopMemoryContext attached
66  * locally to this process.
67  */
69 
70 /* Shared state information for injection points. */
72 {
73  /* Protects access to other fields */
75 
76  /* Counters advancing when injection_points_wakeup() is called */
78 
79  /* Names of injection points attached to wait counters */
81 
82  /* Condition variable used for waits and wakeups */
85 
86 /* Pointer to shared-memory state. */
88 
89 extern PGDLLEXPORT void injection_error(const char *name,
90  const void *private_data);
91 extern PGDLLEXPORT void injection_notice(const char *name,
92  const void *private_data);
93 extern PGDLLEXPORT void injection_wait(const char *name,
94  const void *private_data);
95 
96 /* track if injection points attached in this process are linked to it */
97 static bool injection_point_local = false;
98 
99 /*
100  * Callback for shared memory area initialization.
101  */
102 static void
104 {
106 
107  SpinLockInit(&state->lock);
108  memset(state->wait_counts, 0, sizeof(state->wait_counts));
109  memset(state->name, 0, sizeof(state->name));
110  ConditionVariableInit(&state->wait_point);
111 }
112 
113 /*
114  * Initialize shared memory area for this module.
115  */
116 static void
118 {
119  bool found;
120 
121  if (inj_state != NULL)
122  return;
123 
124  inj_state = GetNamedDSMSegment("injection_points",
127  &found);
128 }
129 
130 /*
131  * Check runtime conditions associated to an injection point.
132  *
133  * Returns true if the named injection point is allowed to run, and false
134  * otherwise.
135  */
136 static bool
138 {
139  bool result = true;
140 
141  switch (condition->type)
142  {
143  case INJ_CONDITION_PID:
144  if (MyProcPid != condition->pid)
145  result = false;
146  break;
148  break;
149  }
150 
151  return result;
152 }
153 
154 /*
155  * before_shmem_exit callback to remove injection points linked to a
156  * specific process.
157  */
158 static void
160 {
161  ListCell *lc;
162 
163  /* Leave if nothing is tracked locally */
165  return;
166 
167  /* Detach all the local points */
168  foreach(lc, inj_list_local)
169  {
170  char *name = strVal(lfirst(lc));
171 
172  (void) InjectionPointDetach(name);
173  }
174 }
175 
176 /* Set of callbacks available to be attached to an injection point. */
177 void
178 injection_error(const char *name, const void *private_data)
179 {
180  InjectionPointCondition *condition = (InjectionPointCondition *) private_data;
181 
182  if (!injection_point_allowed(condition))
183  return;
184 
185  elog(ERROR, "error triggered for injection point %s", name);
186 }
187 
188 void
189 injection_notice(const char *name, const void *private_data)
190 {
191  InjectionPointCondition *condition = (InjectionPointCondition *) private_data;
192 
193  if (!injection_point_allowed(condition))
194  return;
195 
196  elog(NOTICE, "notice triggered for injection point %s", name);
197 }
198 
199 /* Wait on a condition variable, awaken by injection_points_wakeup() */
200 void
201 injection_wait(const char *name, const void *private_data)
202 {
203  uint32 old_wait_counts = 0;
204  int index = -1;
205  uint32 injection_wait_event = 0;
206  InjectionPointCondition *condition = (InjectionPointCondition *) private_data;
207 
208  if (inj_state == NULL)
210 
211  if (!injection_point_allowed(condition))
212  return;
213 
214  /*
215  * Use the injection point name for this custom wait event. Note that
216  * this custom wait event name is not released, but we don't care much for
217  * testing as this should be short-lived.
218  */
219  injection_wait_event = WaitEventExtensionNew(name);
220 
221  /*
222  * Find a free slot to wait for, and register this injection point's name.
223  */
225  for (int i = 0; i < INJ_MAX_WAIT; i++)
226  {
227  if (inj_state->name[i][0] == '\0')
228  {
229  index = i;
231  old_wait_counts = inj_state->wait_counts[i];
232  break;
233  }
234  }
236 
237  if (index < 0)
238  elog(ERROR, "could not find free slot for wait of injection point %s ",
239  name);
240 
241  /* And sleep.. */
243  for (;;)
244  {
245  uint32 new_wait_counts;
246 
248  new_wait_counts = inj_state->wait_counts[index];
250 
251  if (old_wait_counts != new_wait_counts)
252  break;
253  ConditionVariableSleep(&inj_state->wait_point, injection_wait_event);
254  }
256 
257  /* Remove this injection point from the waiters. */
259  inj_state->name[index][0] = '\0';
261 }
262 
263 /*
264  * SQL function for creating an injection point.
265  */
267 Datum
269 {
272  char *function;
273  InjectionPointCondition condition = {0};
274 
275  if (strcmp(action, "error") == 0)
276  function = "injection_error";
277  else if (strcmp(action, "notice") == 0)
278  function = "injection_notice";
279  else if (strcmp(action, "wait") == 0)
280  function = "injection_wait";
281  else
282  elog(ERROR, "incorrect action \"%s\" for injection point creation", action);
283 
285  {
286  condition.type = INJ_CONDITION_PID;
287  condition.pid = MyProcPid;
288  }
289 
290  InjectionPointAttach(name, "injection_points", function, &condition,
291  sizeof(InjectionPointCondition));
292 
294  {
295  MemoryContext oldctx;
296 
297  /* Local injection point, so track it for automated cleanup */
300  MemoryContextSwitchTo(oldctx);
301  }
302  PG_RETURN_VOID();
303 }
304 
305 /*
306  * SQL function for triggering an injection point.
307  */
309 Datum
311 {
313 
315 
316  PG_RETURN_VOID();
317 }
318 
319 /*
320  * SQL function for waking up an injection point waiting in injection_wait().
321  */
323 Datum
325 {
327  int index = -1;
328 
329  if (inj_state == NULL)
331 
332  /* First bump the wait counter for the injection point to wake up */
334  for (int i = 0; i < INJ_MAX_WAIT; i++)
335  {
336  if (strcmp(name, inj_state->name[i]) == 0)
337  {
338  index = i;
339  break;
340  }
341  }
342  if (index < 0)
343  {
345  elog(ERROR, "could not find injection point %s to wake up", name);
346  }
349 
350  /* And broadcast the change to the waiters */
352  PG_RETURN_VOID();
353 }
354 
355 /*
356  * injection_points_set_local
357  *
358  * Track if any injection point created in this process ought to run only
359  * in this process. Such injection points are detached automatically when
360  * this process exits. This is useful to make test suites concurrent-safe.
361  */
363 Datum
365 {
366  /* Enable flag to add a runtime condition based on this process ID */
367  injection_point_local = true;
368 
369  if (inj_state == NULL)
371 
372  /*
373  * Register a before_shmem_exit callback to remove any injection points
374  * linked to this process.
375  */
377 
378  PG_RETURN_VOID();
379 }
380 
381 /*
382  * SQL function for dropping an injection point.
383  */
385 Datum
387 {
389 
391  elog(ERROR, "could not detach injection point \"%s\"", name);
392 
393  /* Remove point from local list, if required */
394  if (inj_list_local != NIL)
395  {
396  MemoryContext oldctx;
397 
400  MemoryContextSwitchTo(oldctx);
401  }
402 
403  PG_RETURN_VOID();
404 }
unsigned int uint32
Definition: c.h:506
#define PGDLLEXPORT
Definition: c.h:1331
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:224
#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:45
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)
Datum injection_points_detach(PG_FUNCTION_ARGS)
static bool injection_point_local
#define INJ_MAX_WAIT
PG_FUNCTION_INFO_V1(injection_points_attach)
PGDLLEXPORT void injection_wait(const char *name, const void *private_data)
InjectionPointConditionType
@ INJ_CONDITION_PID
@ INJ_CONDITION_ALWAYS
static void injection_init_shmem(void)
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 bool injection_point_allowed(InjectionPointCondition *condition)
#define INJ_NAME_MAXLEN
static void injection_points_cleanup(int code, Datum arg)
PGDLLEXPORT void injection_notice(const char *name, const void *private_data)
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
void before_shmem_exit(pg_on_exit_callback function, Datum arg)
Definition: ipc.c:337
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
char * pstrdup(const char *in)
Definition: mcxt.c:1695
MemoryContext TopMemoryContext
Definition: mcxt.c:149
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)
int slock_t
Definition: s_lock.h:735
#define SpinLockInit(lock)
Definition: spin.h:60
#define SpinLockRelease(lock)
Definition: spin.h:64
#define SpinLockAcquire(lock)
Definition: spin.h:62
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 WaitEventExtensionNew(const char *wait_event_name)
Definition: wait_event.c:161
const char * name