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"
23 #include "storage/dsm_registry.h"
24 #include "storage/ipc.h"
25 #include "storage/lwlock.h"
26 #include "storage/shmem.h"
27 #include "utils/builtins.h"
28 #include "utils/injection_point.h"
29 #include "utils/wait_event.h"
30 
32 
33 /* Maximum number of waits usable in injection points at once */
34 #define INJ_MAX_WAIT 8
35 #define INJ_NAME_MAXLEN 64
36 #define INJ_MAX_CONDITION 4
37 
38 /*
39  * Conditions related to injection points. This tracks in shared memory the
40  * runtime conditions under which an injection point is allowed to run.
41  *
42  * If more types of runtime conditions need to be tracked, this structure
43  * should be expanded.
44  */
46 {
47  /* Name of the injection point related to this condition */
49 
50  /* ID of the process where the injection point is allowed to run */
51  int pid;
53 
54 /* Shared state information for injection points. */
56 {
57  /* Protects access to other fields */
59 
60  /* Counters advancing when injection_points_wakeup() is called */
62 
63  /* Names of injection points attached to wait counters */
65 
66  /* Condition variable used for waits and wakeups */
68 
69  /* Conditions to run an injection point */
72 
73 /* Pointer to shared-memory state. */
75 
76 extern PGDLLEXPORT void injection_error(const char *name);
77 extern PGDLLEXPORT void injection_notice(const char *name);
78 extern PGDLLEXPORT void injection_wait(const char *name);
79 
80 /* track if injection points attached in this process are linked to it */
81 static bool injection_point_local = false;
82 
83 /*
84  * Callback for shared memory area initialization.
85  */
86 static void
88 {
90 
91  SpinLockInit(&state->lock);
92  memset(state->wait_counts, 0, sizeof(state->wait_counts));
93  memset(state->name, 0, sizeof(state->name));
94  memset(state->conditions, 0, sizeof(state->conditions));
95  ConditionVariableInit(&state->wait_point);
96 }
97 
98 /*
99  * Initialize shared memory area for this module.
100  */
101 static void
103 {
104  bool found;
105 
106  if (inj_state != NULL)
107  return;
108 
109  inj_state = GetNamedDSMSegment("injection_points",
112  &found);
113 }
114 
115 /*
116  * Check runtime conditions associated to an injection point.
117  *
118  * Returns true if the named injection point is allowed to run, and false
119  * otherwise. Multiple conditions can be associated to a single injection
120  * point, so check them all.
121  */
122 static bool
124 {
125  bool result = true;
126 
127  if (inj_state == NULL)
129 
131 
132  for (int i = 0; i < INJ_MAX_CONDITION; i++)
133  {
135 
136  if (strcmp(condition->name, name) == 0)
137  {
138  /*
139  * Check if this injection point is allowed to run in this
140  * process.
141  */
142  if (MyProcPid != condition->pid)
143  {
144  result = false;
145  break;
146  }
147  }
148  }
149 
151 
152  return result;
153 }
154 
155 /*
156  * before_shmem_exit callback to remove injection points linked to a
157  * specific process.
158  */
159 static void
161 {
162  /* Leave if nothing is tracked locally */
164  return;
165 
167  for (int i = 0; i < INJ_MAX_CONDITION; i++)
168  {
170 
171  if (condition->name[0] == '\0')
172  continue;
173 
174  if (condition->pid != MyProcPid)
175  continue;
176 
177  /* Detach the injection point and unregister condition */
178  InjectionPointDetach(condition->name);
179  condition->name[0] = '\0';
180  condition->pid = 0;
181  }
183 }
184 
185 /* Set of callbacks available to be attached to an injection point. */
186 void
187 injection_error(const char *name)
188 {
190  return;
191 
192  elog(ERROR, "error triggered for injection point %s", name);
193 }
194 
195 void
196 injection_notice(const char *name)
197 {
199  return;
200 
201  elog(NOTICE, "notice triggered for injection point %s", name);
202 }
203 
204 /* Wait on a condition variable, awaken by injection_points_wakeup() */
205 void
206 injection_wait(const char *name)
207 {
208  uint32 old_wait_counts = 0;
209  int index = -1;
210  uint32 injection_wait_event = 0;
211 
212  if (inj_state == NULL)
214 
216  return;
217 
218  /*
219  * Use the injection point name for this custom wait event. Note that
220  * this custom wait event name is not released, but we don't care much for
221  * testing as this should be short-lived.
222  */
223  injection_wait_event = WaitEventExtensionNew(name);
224 
225  /*
226  * Find a free slot to wait for, and register this injection point's name.
227  */
229  for (int i = 0; i < INJ_MAX_WAIT; i++)
230  {
231  if (inj_state->name[i][0] == '\0')
232  {
233  index = i;
235  old_wait_counts = inj_state->wait_counts[i];
236  break;
237  }
238  }
240 
241  if (index < 0)
242  elog(ERROR, "could not find free slot for wait of injection point %s ",
243  name);
244 
245  /* And sleep.. */
247  for (;;)
248  {
249  uint32 new_wait_counts;
250 
252  new_wait_counts = inj_state->wait_counts[index];
254 
255  if (old_wait_counts != new_wait_counts)
256  break;
257  ConditionVariableSleep(&inj_state->wait_point, injection_wait_event);
258  }
260 
261  /* Remove this injection point from the waiters. */
263  inj_state->name[index][0] = '\0';
265 }
266 
267 /*
268  * SQL function for creating an injection point.
269  */
271 Datum
273 {
276  char *function;
277 
278  if (strcmp(action, "error") == 0)
279  function = "injection_error";
280  else if (strcmp(action, "notice") == 0)
281  function = "injection_notice";
282  else if (strcmp(action, "wait") == 0)
283  function = "injection_wait";
284  else
285  elog(ERROR, "incorrect action \"%s\" for injection point creation", action);
286 
287  InjectionPointAttach(name, "injection_points", function);
288 
290  {
291  int index = -1;
292 
293  /*
294  * Register runtime condition to link this injection point to the
295  * current process.
296  */
298  for (int i = 0; i < INJ_MAX_CONDITION; i++)
299  {
301 
302  if (condition->name[0] == '\0')
303  {
304  index = i;
305  strlcpy(condition->name, name, INJ_NAME_MAXLEN);
306  condition->pid = MyProcPid;
307  break;
308  }
309  }
311 
312  if (index < 0)
313  elog(FATAL,
314  "could not find free slot for condition of injection point %s",
315  name);
316  }
317 
318  PG_RETURN_VOID();
319 }
320 
321 /*
322  * SQL function for triggering an injection point.
323  */
325 Datum
327 {
329 
331 
332  PG_RETURN_VOID();
333 }
334 
335 /*
336  * SQL function for waking up an injection point waiting in injection_wait().
337  */
339 Datum
341 {
343  int index = -1;
344 
345  if (inj_state == NULL)
347 
348  /* First bump the wait counter for the injection point to wake up */
350  for (int i = 0; i < INJ_MAX_WAIT; i++)
351  {
352  if (strcmp(name, inj_state->name[i]) == 0)
353  {
354  index = i;
355  break;
356  }
357  }
358  if (index < 0)
359  {
361  elog(ERROR, "could not find injection point %s to wake up", name);
362  }
365 
366  /* And broadcast the change to the waiters */
368  PG_RETURN_VOID();
369 }
370 
371 /*
372  * injection_points_set_local
373  *
374  * Track if any injection point created in this process ought to run only
375  * in this process. Such injection points are detached automatically when
376  * this process exits. This is useful to make test suites concurrent-safe.
377  */
379 Datum
381 {
382  /* Enable flag to add a runtime condition based on this process ID */
383  injection_point_local = true;
384 
385  if (inj_state == NULL)
387 
388  /*
389  * Register a before_shmem_exit callback to remove any injection points
390  * linked to this process.
391  */
393 
394  PG_RETURN_VOID();
395 }
396 
397 /*
398  * SQL function for dropping an injection point.
399  */
401 Datum
403 {
405 
407 
408  if (inj_state == NULL)
410 
411  /* Clean up any conditions associated to this injection point */
413  for (int i = 0; i < INJ_MAX_CONDITION; i++)
414  {
416 
417  if (strcmp(condition->name, name) == 0)
418  {
419  condition->pid = 0;
420  condition->name[0] = '\0';
421  }
422  }
424 
425  PG_RETURN_VOID();
426 }
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 FATAL
Definition: elog.h:41
#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
void InjectionPointDetach(const char *name)
void InjectionPointAttach(const char *name, const char *library, const char *function)
#define INJECTION_POINT(name)
Datum injection_points_detach(PG_FUNCTION_ARGS)
static bool injection_point_local
PGDLLEXPORT void injection_error(const char *name)
#define INJ_MAX_WAIT
PGDLLEXPORT void injection_notice(const char *name)
PG_FUNCTION_INFO_V1(injection_points_attach)
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)
Datum injection_points_set_local(PG_FUNCTION_ARGS)
#define INJ_NAME_MAXLEN
static bool injection_point_allowed(const char *name)
static void injection_points_cleanup(int code, Datum arg)
#define INJ_MAX_CONDITION
struct InjectionPointSharedState InjectionPointSharedState
Datum injection_points_wakeup(PG_FUNCTION_ARGS)
PGDLLEXPORT void injection_wait(const char *name)
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
void * arg
size_t strlcpy(char *dst, const char *src, size_t siz)
Definition: strlcpy.c:45
uintptr_t Datum
Definition: postgres.h:64
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
char name[INJ_NAME_MAXLEN]
uint32 wait_counts[INJ_MAX_WAIT]
InjectionPointCondition conditions[INJ_MAX_CONDITION]
char name[INJ_MAX_WAIT][INJ_NAME_MAXLEN]
ConditionVariable wait_point
Definition: type.h:95
Definition: regguts.h:323
char * text_to_cstring(const text *t)
Definition: varlena.c:217
uint32 WaitEventExtensionNew(const char *wait_event_name)
Definition: wait_event.c:162
const char * name