Repair ALTER EXTENSION ... SET SCHEMA.
[pgsql.git] / src / test / modules / injection_points / injection_points.c
bloba74a4a28afda02aca351b63669b68ad3c6fddf7e
1 /*--------------------------------------------------------------------------
3 * injection_points.c
4 * Code for testing injection points.
6 * Injection points are able to trigger user-defined callbacks in pre-defined
7 * code paths.
9 * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
10 * Portions Copyright (c) 1994, Regents of the University of California
12 * IDENTIFICATION
13 * src/test/modules/injection_points/injection_points.c
15 * -------------------------------------------------------------------------
18 #include "postgres.h"
20 #include "fmgr.h"
21 #include "miscadmin.h"
22 #include "storage/condition_variable.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"
31 PG_MODULE_MAGIC;
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
39 * Conditions related to injection points. This tracks in shared memory the
40 * runtime conditions under which an injection point is allowed to run.
42 * If more types of runtime conditions need to be tracked, this structure
43 * should be expanded.
45 typedef struct InjectionPointCondition
47 /* Name of the injection point related to this condition */
48 char name[INJ_NAME_MAXLEN];
50 /* ID of the process where the injection point is allowed to run */
51 int pid;
52 } InjectionPointCondition;
54 /* Shared state information for injection points. */
55 typedef struct InjectionPointSharedState
57 /* Protects access to other fields */
58 slock_t lock;
60 /* Counters advancing when injection_points_wakeup() is called */
61 uint32 wait_counts[INJ_MAX_WAIT];
63 /* Names of injection points attached to wait counters */
64 char name[INJ_MAX_WAIT][INJ_NAME_MAXLEN];
66 /* Condition variable used for waits and wakeups */
67 ConditionVariable wait_point;
69 /* Conditions to run an injection point */
70 InjectionPointCondition conditions[INJ_MAX_CONDITION];
71 } InjectionPointSharedState;
73 /* Pointer to shared-memory state. */
74 static InjectionPointSharedState *inj_state = NULL;
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);
80 /* track if injection points attached in this process are linked to it */
81 static bool injection_point_local = false;
84 * Callback for shared memory area initialization.
86 static void
87 injection_point_init_state(void *ptr)
89 InjectionPointSharedState *state = (InjectionPointSharedState *) ptr;
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);
99 * Initialize shared memory area for this module.
101 static void
102 injection_init_shmem(void)
104 bool found;
106 if (inj_state != NULL)
107 return;
109 inj_state = GetNamedDSMSegment("injection_points",
110 sizeof(InjectionPointSharedState),
111 injection_point_init_state,
112 &found);
116 * Check runtime conditions associated to an injection point.
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.
122 static bool
123 injection_point_allowed(const char *name)
125 bool result = true;
127 if (inj_state == NULL)
128 injection_init_shmem();
130 SpinLockAcquire(&inj_state->lock);
132 for (int i = 0; i < INJ_MAX_CONDITION; i++)
134 InjectionPointCondition *condition = &inj_state->conditions[i];
136 if (strcmp(condition->name, name) == 0)
139 * Check if this injection point is allowed to run in this
140 * process.
142 if (MyProcPid != condition->pid)
144 result = false;
145 break;
150 SpinLockRelease(&inj_state->lock);
152 return result;
156 * before_shmem_exit callback to remove injection points linked to a
157 * specific process.
159 static void
160 injection_points_cleanup(int code, Datum arg)
162 char names[INJ_MAX_CONDITION][INJ_NAME_MAXLEN] = {0};
163 int count = 0;
165 /* Leave if nothing is tracked locally */
166 if (!injection_point_local)
167 return;
170 * This is done in three steps: detect the points to detach, detach them
171 * and release their conditions.
173 SpinLockAcquire(&inj_state->lock);
174 for (int i = 0; i < INJ_MAX_CONDITION; i++)
176 InjectionPointCondition *condition = &inj_state->conditions[i];
178 if (condition->name[0] == '\0')
179 continue;
181 if (condition->pid != MyProcPid)
182 continue;
184 /* Extract the point name to detach */
185 strlcpy(names[count], condition->name, INJ_NAME_MAXLEN);
186 count++;
188 SpinLockRelease(&inj_state->lock);
190 /* Detach, without holding the spinlock */
191 for (int i = 0; i < count; i++)
192 InjectionPointDetach(names[i]);
194 /* Clear all the conditions */
195 SpinLockAcquire(&inj_state->lock);
196 for (int i = 0; i < INJ_MAX_CONDITION; i++)
198 InjectionPointCondition *condition = &inj_state->conditions[i];
200 if (condition->name[0] == '\0')
201 continue;
203 if (condition->pid != MyProcPid)
204 continue;
206 condition->name[0] = '\0';
207 condition->pid = 0;
209 SpinLockRelease(&inj_state->lock);
212 /* Set of callbacks available to be attached to an injection point. */
213 void
214 injection_error(const char *name)
216 if (!injection_point_allowed(name))
217 return;
219 elog(ERROR, "error triggered for injection point %s", name);
222 void
223 injection_notice(const char *name)
225 if (!injection_point_allowed(name))
226 return;
228 elog(NOTICE, "notice triggered for injection point %s", name);
231 /* Wait on a condition variable, awaken by injection_points_wakeup() */
232 void
233 injection_wait(const char *name)
235 uint32 old_wait_counts = 0;
236 int index = -1;
237 uint32 injection_wait_event = 0;
239 if (inj_state == NULL)
240 injection_init_shmem();
242 if (!injection_point_allowed(name))
243 return;
246 * Use the injection point name for this custom wait event. Note that
247 * this custom wait event name is not released, but we don't care much for
248 * testing as this should be short-lived.
250 injection_wait_event = WaitEventExtensionNew(name);
253 * Find a free slot to wait for, and register this injection point's name.
255 SpinLockAcquire(&inj_state->lock);
256 for (int i = 0; i < INJ_MAX_WAIT; i++)
258 if (inj_state->name[i][0] == '\0')
260 index = i;
261 strlcpy(inj_state->name[i], name, INJ_NAME_MAXLEN);
262 old_wait_counts = inj_state->wait_counts[i];
263 break;
266 SpinLockRelease(&inj_state->lock);
268 if (index < 0)
269 elog(ERROR, "could not find free slot for wait of injection point %s ",
270 name);
272 /* And sleep.. */
273 ConditionVariablePrepareToSleep(&inj_state->wait_point);
274 for (;;)
276 uint32 new_wait_counts;
278 SpinLockAcquire(&inj_state->lock);
279 new_wait_counts = inj_state->wait_counts[index];
280 SpinLockRelease(&inj_state->lock);
282 if (old_wait_counts != new_wait_counts)
283 break;
284 ConditionVariableSleep(&inj_state->wait_point, injection_wait_event);
286 ConditionVariableCancelSleep();
288 /* Remove this injection point from the waiters. */
289 SpinLockAcquire(&inj_state->lock);
290 inj_state->name[index][0] = '\0';
291 SpinLockRelease(&inj_state->lock);
295 * SQL function for creating an injection point.
297 PG_FUNCTION_INFO_V1(injection_points_attach);
298 Datum
299 injection_points_attach(PG_FUNCTION_ARGS)
301 char *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
302 char *action = text_to_cstring(PG_GETARG_TEXT_PP(1));
303 char *function;
305 if (strcmp(action, "error") == 0)
306 function = "injection_error";
307 else if (strcmp(action, "notice") == 0)
308 function = "injection_notice";
309 else if (strcmp(action, "wait") == 0)
310 function = "injection_wait";
311 else
312 elog(ERROR, "incorrect action \"%s\" for injection point creation", action);
314 InjectionPointAttach(name, "injection_points", function);
316 if (injection_point_local)
318 int index = -1;
321 * Register runtime condition to link this injection point to the
322 * current process.
324 SpinLockAcquire(&inj_state->lock);
325 for (int i = 0; i < INJ_MAX_CONDITION; i++)
327 InjectionPointCondition *condition = &inj_state->conditions[i];
329 if (condition->name[0] == '\0')
331 index = i;
332 strlcpy(condition->name, name, INJ_NAME_MAXLEN);
333 condition->pid = MyProcPid;
334 break;
337 SpinLockRelease(&inj_state->lock);
339 if (index < 0)
340 elog(FATAL,
341 "could not find free slot for condition of injection point %s",
342 name);
345 PG_RETURN_VOID();
349 * SQL function for triggering an injection point.
351 PG_FUNCTION_INFO_V1(injection_points_run);
352 Datum
353 injection_points_run(PG_FUNCTION_ARGS)
355 char *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
357 INJECTION_POINT(name);
359 PG_RETURN_VOID();
363 * SQL function for waking up an injection point waiting in injection_wait().
365 PG_FUNCTION_INFO_V1(injection_points_wakeup);
366 Datum
367 injection_points_wakeup(PG_FUNCTION_ARGS)
369 char *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
370 int index = -1;
372 if (inj_state == NULL)
373 injection_init_shmem();
375 /* First bump the wait counter for the injection point to wake up */
376 SpinLockAcquire(&inj_state->lock);
377 for (int i = 0; i < INJ_MAX_WAIT; i++)
379 if (strcmp(name, inj_state->name[i]) == 0)
381 index = i;
382 break;
385 if (index < 0)
387 SpinLockRelease(&inj_state->lock);
388 elog(ERROR, "could not find injection point %s to wake up", name);
390 inj_state->wait_counts[index]++;
391 SpinLockRelease(&inj_state->lock);
393 /* And broadcast the change to the waiters */
394 ConditionVariableBroadcast(&inj_state->wait_point);
395 PG_RETURN_VOID();
399 * injection_points_set_local
401 * Track if any injection point created in this process ought to run only
402 * in this process. Such injection points are detached automatically when
403 * this process exits. This is useful to make test suites concurrent-safe.
405 PG_FUNCTION_INFO_V1(injection_points_set_local);
406 Datum
407 injection_points_set_local(PG_FUNCTION_ARGS)
409 /* Enable flag to add a runtime condition based on this process ID */
410 injection_point_local = true;
412 if (inj_state == NULL)
413 injection_init_shmem();
416 * Register a before_shmem_exit callback to remove any injection points
417 * linked to this process.
419 before_shmem_exit(injection_points_cleanup, (Datum) 0);
421 PG_RETURN_VOID();
425 * SQL function for dropping an injection point.
427 PG_FUNCTION_INFO_V1(injection_points_detach);
428 Datum
429 injection_points_detach(PG_FUNCTION_ARGS)
431 char *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
433 InjectionPointDetach(name);
435 if (inj_state == NULL)
436 injection_init_shmem();
438 /* Clean up any conditions associated to this injection point */
439 SpinLockAcquire(&inj_state->lock);
440 for (int i = 0; i < INJ_MAX_CONDITION; i++)
442 InjectionPointCondition *condition = &inj_state->conditions[i];
444 if (strcmp(condition->name, name) == 0)
446 condition->pid = 0;
447 condition->name[0] = '\0';
450 SpinLockRelease(&inj_state->lock);
452 PG_RETURN_VOID();