1 /*--------------------------------------------------------------------------
4 * Code for testing injection points.
6 * Injection points are able to trigger user-defined callbacks in pre-defined
9 * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
10 * Portions Copyright (c) 1994, Regents of the University of California
13 * src/test/modules/injection_points/injection_points.c
15 * -------------------------------------------------------------------------
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"
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
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 */
52 } InjectionPointCondition
;
54 /* Shared state information for injection points. */
55 typedef struct InjectionPointSharedState
57 /* Protects access to other fields */
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.
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.
102 injection_init_shmem(void)
106 if (inj_state
!= NULL
)
109 inj_state
= GetNamedDSMSegment("injection_points",
110 sizeof(InjectionPointSharedState
),
111 injection_point_init_state
,
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.
123 injection_point_allowed(const char *name
)
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
142 if (MyProcPid
!= condition
->pid
)
150 SpinLockRelease(&inj_state
->lock
);
156 * before_shmem_exit callback to remove injection points linked to a
160 injection_points_cleanup(int code
, Datum arg
)
162 char names
[INJ_MAX_CONDITION
][INJ_NAME_MAXLEN
] = {0};
165 /* Leave if nothing is tracked locally */
166 if (!injection_point_local
)
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')
181 if (condition
->pid
!= MyProcPid
)
184 /* Extract the point name to detach */
185 strlcpy(names
[count
], condition
->name
, INJ_NAME_MAXLEN
);
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')
203 if (condition
->pid
!= MyProcPid
)
206 condition
->name
[0] = '\0';
209 SpinLockRelease(&inj_state
->lock
);
212 /* Set of callbacks available to be attached to an injection point. */
214 injection_error(const char *name
)
216 if (!injection_point_allowed(name
))
219 elog(ERROR
, "error triggered for injection point %s", name
);
223 injection_notice(const char *name
)
225 if (!injection_point_allowed(name
))
228 elog(NOTICE
, "notice triggered for injection point %s", name
);
231 /* Wait on a condition variable, awaken by injection_points_wakeup() */
233 injection_wait(const char *name
)
235 uint32 old_wait_counts
= 0;
237 uint32 injection_wait_event
= 0;
239 if (inj_state
== NULL
)
240 injection_init_shmem();
242 if (!injection_point_allowed(name
))
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')
261 strlcpy(inj_state
->name
[i
], name
, INJ_NAME_MAXLEN
);
262 old_wait_counts
= inj_state
->wait_counts
[i
];
266 SpinLockRelease(&inj_state
->lock
);
269 elog(ERROR
, "could not find free slot for wait of injection point %s ",
273 ConditionVariablePrepareToSleep(&inj_state
->wait_point
);
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
)
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
);
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));
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";
312 elog(ERROR
, "incorrect action \"%s\" for injection point creation", action
);
314 InjectionPointAttach(name
, "injection_points", function
);
316 if (injection_point_local
)
321 * Register runtime condition to link this injection point to the
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')
332 strlcpy(condition
->name
, name
, INJ_NAME_MAXLEN
);
333 condition
->pid
= MyProcPid
;
337 SpinLockRelease(&inj_state
->lock
);
341 "could not find free slot for condition of injection point %s",
349 * SQL function for triggering an injection point.
351 PG_FUNCTION_INFO_V1(injection_points_run
);
353 injection_points_run(PG_FUNCTION_ARGS
)
355 char *name
= text_to_cstring(PG_GETARG_TEXT_PP(0));
357 INJECTION_POINT(name
);
363 * SQL function for waking up an injection point waiting in injection_wait().
365 PG_FUNCTION_INFO_V1(injection_points_wakeup
);
367 injection_points_wakeup(PG_FUNCTION_ARGS
)
369 char *name
= text_to_cstring(PG_GETARG_TEXT_PP(0));
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)
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
);
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
);
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);
425 * SQL function for dropping an injection point.
427 PG_FUNCTION_INFO_V1(injection_points_detach
);
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)
447 condition
->name
[0] = '\0';
450 SpinLockRelease(&inj_state
->lock
);