Age Owner Branch data TLA Line data Source code
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 "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"
30 : :
83 michael@paquier.xyz 31 :GNC 14 : PG_MODULE_MAGIC;
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 : : */
45 : : typedef struct InjectionPointCondition
46 : : {
47 : : /* Name of the injection point related to this condition */
48 : : char name[INJ_NAME_MAXLEN];
49 : :
50 : : /* ID of the process where the injection point is allowed to run */
51 : : int pid;
52 : : } InjectionPointCondition;
53 : :
54 : : /* Shared state information for injection points. */
55 : : typedef struct InjectionPointSharedState
56 : : {
57 : : /* Protects access to other fields */
58 : : slock_t lock;
59 : :
60 : : /* Counters advancing when injection_points_wakeup() is called */
61 : : uint32 wait_counts[INJ_MAX_WAIT];
62 : :
63 : : /* Names of injection points attached to wait counters */
64 : : char name[INJ_MAX_WAIT][INJ_NAME_MAXLEN];
65 : :
66 : : /* Condition variable used for waits and wakeups */
67 : : ConditionVariable wait_point;
68 : :
69 : : /* Conditions to run an injection point */
70 : : InjectionPointCondition conditions[INJ_MAX_CONDITION];
71 : : } InjectionPointSharedState;
72 : :
73 : : /* Pointer to shared-memory state. */
74 : : static InjectionPointSharedState *inj_state = NULL;
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
41 87 : 3 : injection_point_init_state(void *ptr)
88 : : {
89 : 3 : InjectionPointSharedState *state = (InjectionPointSharedState *) ptr;
90 : :
91 : 3 : SpinLockInit(&state->lock);
92 : 3 : memset(state->wait_counts, 0, sizeof(state->wait_counts));
93 : 3 : memset(state->name, 0, sizeof(state->name));
6 94 : 3 : memset(state->conditions, 0, sizeof(state->conditions));
41 95 : 3 : ConditionVariableInit(&state->wait_point);
96 : 3 : }
97 : :
98 : : /*
99 : : * Initialize shared memory area for this module.
100 : : */
101 : : static void
102 : 8 : injection_init_shmem(void)
103 : : {
104 : : bool found;
105 : :
106 [ - + ]: 8 : if (inj_state != NULL)
41 michael@paquier.xyz 107 :UNC 0 : return;
108 : :
41 michael@paquier.xyz 109 :GNC 8 : inj_state = GetNamedDSMSegment("injection_points",
110 : : sizeof(InjectionPointSharedState),
111 : : injection_point_init_state,
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
6 123 : 18 : injection_point_allowed(const char *name)
124 : : {
125 : 18 : bool result = true;
126 : :
127 [ + + ]: 18 : if (inj_state == NULL)
128 : 3 : injection_init_shmem();
129 : :
130 [ - + ]: 18 : SpinLockAcquire(&inj_state->lock);
131 : :
132 [ + + ]: 90 : for (int i = 0; i < INJ_MAX_CONDITION; i++)
133 : : {
134 : 72 : InjectionPointCondition *condition = &inj_state->conditions[i];
135 : :
136 [ + + ]: 72 : if (strcmp(condition->name, name) == 0)
137 : : {
138 : : /*
139 : : * Check if this injection point is allowed to run in this
140 : : * process.
141 : : */
142 [ - + ]: 6 : if (MyProcPid != condition->pid)
143 : : {
6 michael@paquier.xyz 144 :UNC 0 : result = false;
145 : 0 : break;
146 : : }
147 : : }
148 : : }
149 : :
6 michael@paquier.xyz 150 :GNC 18 : SpinLockRelease(&inj_state->lock);
151 : :
152 : 18 : return result;
153 : : }
154 : :
155 : : /*
156 : : * before_shmem_exit callback to remove injection points linked to a
157 : : * specific process.
158 : : */
159 : : static void
160 : 2 : injection_points_cleanup(int code, Datum arg)
161 : : {
162 : : /* Leave if nothing is tracked locally */
163 [ - + ]: 2 : if (!injection_point_local)
6 michael@paquier.xyz 164 :UNC 0 : return;
165 : :
6 michael@paquier.xyz 166 [ - + ]:GNC 2 : SpinLockAcquire(&inj_state->lock);
167 [ + + ]: 10 : for (int i = 0; i < INJ_MAX_CONDITION; i++)
168 : : {
169 : 8 : InjectionPointCondition *condition = &inj_state->conditions[i];
170 : :
171 [ + + ]: 8 : if (condition->name[0] == '\0')
172 : 6 : continue;
173 : :
174 [ - + ]: 2 : if (condition->pid != MyProcPid)
6 michael@paquier.xyz 175 :UNC 0 : continue;
176 : :
177 : : /* Detach the injection point and unregister condition */
6 michael@paquier.xyz 178 :GNC 2 : InjectionPointDetach(condition->name);
179 : 2 : condition->name[0] = '\0';
180 : 2 : condition->pid = 0;
181 : : }
182 : 2 : SpinLockRelease(&inj_state->lock);
183 : : }
184 : :
185 : : /* Set of callbacks available to be attached to an injection point. */
186 : : void
83 187 : 6 : injection_error(const char *name)
188 : : {
6 189 [ - + ]: 6 : if (!injection_point_allowed(name))
6 michael@paquier.xyz 190 :UNC 0 : return;
191 : :
83 michael@paquier.xyz 192 [ + - ]:GNC 6 : elog(ERROR, "error triggered for injection point %s", name);
193 : : }
194 : :
195 : : void
196 : 10 : injection_notice(const char *name)
197 : : {
6 198 [ - + ]: 10 : if (!injection_point_allowed(name))
6 michael@paquier.xyz 199 :UNC 0 : return;
200 : :
83 michael@paquier.xyz 201 [ + - ]:GNC 10 : elog(NOTICE, "notice triggered for injection point %s", name);
202 : : }
203 : :
204 : : /* Wait on a condition variable, awaken by injection_points_wakeup() */
205 : : void
41 206 : 2 : injection_wait(const char *name)
207 : : {
208 : 2 : uint32 old_wait_counts = 0;
209 : 2 : int index = -1;
210 : 2 : uint32 injection_wait_event = 0;
211 : :
212 [ + - ]: 2 : if (inj_state == NULL)
213 : 2 : injection_init_shmem();
214 : :
6 215 [ - + ]: 2 : if (!injection_point_allowed(name))
6 michael@paquier.xyz 216 :UNC 0 : 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 : : */
41 michael@paquier.xyz 223 :GNC 2 : injection_wait_event = WaitEventExtensionNew(name);
224 : :
225 : : /*
226 : : * Find a free slot to wait for, and register this injection point's name.
227 : : */
228 [ - + ]: 2 : SpinLockAcquire(&inj_state->lock);
229 [ + - ]: 2 : for (int i = 0; i < INJ_MAX_WAIT; i++)
230 : : {
231 [ + - ]: 2 : if (inj_state->name[i][0] == '\0')
232 : : {
233 : 2 : index = i;
234 : 2 : strlcpy(inj_state->name[i], name, INJ_NAME_MAXLEN);
235 : 2 : old_wait_counts = inj_state->wait_counts[i];
236 : 2 : break;
237 : : }
238 : : }
239 : 2 : SpinLockRelease(&inj_state->lock);
240 : :
241 [ - + ]: 2 : if (index < 0)
41 michael@paquier.xyz 242 [ # # ]:UNC 0 : elog(ERROR, "could not find free slot for wait of injection point %s ",
243 : : name);
244 : :
245 : : /* And sleep.. */
41 michael@paquier.xyz 246 :GNC 2 : ConditionVariablePrepareToSleep(&inj_state->wait_point);
247 : : for (;;)
248 : 2 : {
249 : : uint32 new_wait_counts;
250 : :
251 [ - + ]: 4 : SpinLockAcquire(&inj_state->lock);
252 : 4 : new_wait_counts = inj_state->wait_counts[index];
253 : 4 : SpinLockRelease(&inj_state->lock);
254 : :
255 [ + + ]: 4 : if (old_wait_counts != new_wait_counts)
256 : 2 : break;
257 : 2 : ConditionVariableSleep(&inj_state->wait_point, injection_wait_event);
258 : : }
259 : 2 : ConditionVariableCancelSleep();
260 : :
261 : : /* Remove this injection point from the waiters. */
262 [ - + ]: 2 : SpinLockAcquire(&inj_state->lock);
263 : 2 : inj_state->name[index][0] = '\0';
264 : 2 : SpinLockRelease(&inj_state->lock);
265 : : }
266 : :
267 : : /*
268 : : * SQL function for creating an injection point.
269 : : */
83 270 : 12 : PG_FUNCTION_INFO_V1(injection_points_attach);
271 : : Datum
272 : 15 : injection_points_attach(PG_FUNCTION_ARGS)
273 : : {
274 : 15 : char *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
275 : 15 : char *action = text_to_cstring(PG_GETARG_TEXT_PP(1));
276 : : char *function;
277 : :
278 [ + + ]: 15 : if (strcmp(action, "error") == 0)
279 : 6 : function = "injection_error";
280 [ + + ]: 9 : else if (strcmp(action, "notice") == 0)
281 : 4 : function = "injection_notice";
41 282 [ + + ]: 5 : else if (strcmp(action, "wait") == 0)
283 : 4 : function = "injection_wait";
284 : : else
83 285 [ + - ]: 1 : elog(ERROR, "incorrect action \"%s\" for injection point creation", action);
286 : :
287 : 14 : InjectionPointAttach(name, "injection_points", function);
288 : :
6 289 [ + + ]: 14 : if (injection_point_local)
290 : : {
291 : 5 : int index = -1;
292 : :
293 : : /*
294 : : * Register runtime condition to link this injection point to the
295 : : * current process.
296 : : */
297 [ - + ]: 5 : SpinLockAcquire(&inj_state->lock);
298 [ + - ]: 8 : for (int i = 0; i < INJ_MAX_CONDITION; i++)
299 : : {
300 : 8 : InjectionPointCondition *condition = &inj_state->conditions[i];
301 : :
302 [ + + ]: 8 : if (condition->name[0] == '\0')
303 : : {
304 : 5 : index = i;
305 : 5 : strlcpy(condition->name, name, INJ_NAME_MAXLEN);
306 : 5 : condition->pid = MyProcPid;
307 : 5 : break;
308 : : }
309 : : }
310 : 5 : SpinLockRelease(&inj_state->lock);
311 : :
312 [ - + ]: 5 : if (index < 0)
6 michael@paquier.xyz 313 [ # # ]:UNC 0 : elog(FATAL,
314 : : "could not find free slot for condition of injection point %s",
315 : : name);
316 : : }
317 : :
83 michael@paquier.xyz 318 :GNC 14 : PG_RETURN_VOID();
319 : : }
320 : :
321 : : /*
322 : : * SQL function for triggering an injection point.
323 : : */
324 : 7 : PG_FUNCTION_INFO_V1(injection_points_run);
325 : : Datum
326 : 18 : injection_points_run(PG_FUNCTION_ARGS)
327 : : {
328 : 18 : char *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
329 : :
330 : 18 : INJECTION_POINT(name);
331 : :
332 : 14 : PG_RETURN_VOID();
333 : : }
334 : :
335 : : /*
336 : : * SQL function for waking up an injection point waiting in injection_wait().
337 : : */
41 338 : 6 : PG_FUNCTION_INFO_V1(injection_points_wakeup);
339 : : Datum
340 : 2 : injection_points_wakeup(PG_FUNCTION_ARGS)
341 : : {
342 : 2 : char *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
343 : 2 : int index = -1;
344 : :
345 [ + - ]: 2 : if (inj_state == NULL)
346 : 2 : injection_init_shmem();
347 : :
348 : : /* First bump the wait counter for the injection point to wake up */
349 [ - + ]: 2 : SpinLockAcquire(&inj_state->lock);
350 [ + - ]: 2 : for (int i = 0; i < INJ_MAX_WAIT; i++)
351 : : {
352 [ + - ]: 2 : if (strcmp(name, inj_state->name[i]) == 0)
353 : : {
354 : 2 : index = i;
355 : 2 : break;
356 : : }
357 : : }
358 [ - + ]: 2 : if (index < 0)
359 : : {
41 michael@paquier.xyz 360 :UNC 0 : SpinLockRelease(&inj_state->lock);
361 [ # # ]: 0 : elog(ERROR, "could not find injection point %s to wake up", name);
362 : : }
41 michael@paquier.xyz 363 :GNC 2 : inj_state->wait_counts[index]++;
364 : 2 : SpinLockRelease(&inj_state->lock);
365 : :
366 : : /* And broadcast the change to the waiters */
367 : 2 : ConditionVariableBroadcast(&inj_state->wait_point);
368 : 2 : 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 : : */
6 378 : 6 : PG_FUNCTION_INFO_V1(injection_points_set_local);
379 : : Datum
380 : 2 : injection_points_set_local(PG_FUNCTION_ARGS)
381 : : {
382 : : /* Enable flag to add a runtime condition based on this process ID */
383 : 2 : injection_point_local = true;
384 : :
385 [ + + ]: 2 : if (inj_state == NULL)
386 : 1 : injection_init_shmem();
387 : :
388 : : /*
389 : : * Register a before_shmem_exit callback to remove any injection points
390 : : * linked to this process.
391 : : */
392 : 2 : before_shmem_exit(injection_points_cleanup, (Datum) 0);
393 : :
394 : 2 : PG_RETURN_VOID();
395 : : }
396 : :
397 : : /*
398 : : * SQL function for dropping an injection point.
399 : : */
83 400 : 7 : PG_FUNCTION_INFO_V1(injection_points_detach);
401 : : Datum
402 : 9 : injection_points_detach(PG_FUNCTION_ARGS)
403 : : {
404 : 9 : char *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
405 : :
406 : 9 : InjectionPointDetach(name);
407 : :
6 408 [ - + ]: 8 : if (inj_state == NULL)
6 michael@paquier.xyz 409 :UNC 0 : injection_init_shmem();
410 : :
411 : : /* Clean up any conditions associated to this injection point */
6 michael@paquier.xyz 412 [ - + ]:GNC 8 : SpinLockAcquire(&inj_state->lock);
413 [ + + ]: 40 : for (int i = 0; i < INJ_MAX_CONDITION; i++)
414 : : {
415 : 32 : InjectionPointCondition *condition = &inj_state->conditions[i];
416 : :
417 [ + + ]: 32 : if (strcmp(condition->name, name) == 0)
418 : : {
419 : 3 : condition->pid = 0;
420 : 3 : condition->name[0] = '\0';
421 : : }
422 : : }
423 : 8 : SpinLockRelease(&inj_state->lock);
424 : :
83 425 : 8 : PG_RETURN_VOID();
426 : : }
|