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-2025, 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 "injection_stats.h"
22 : #include "miscadmin.h"
23 : #include "nodes/pg_list.h"
24 : #include "nodes/value.h"
25 : #include "storage/condition_variable.h"
26 : #include "storage/dsm_registry.h"
27 : #include "storage/ipc.h"
28 : #include "storage/lwlock.h"
29 : #include "storage/shmem.h"
30 : #include "utils/builtins.h"
31 : #include "utils/guc.h"
32 : #include "utils/injection_point.h"
33 : #include "utils/memutils.h"
34 : #include "utils/wait_event.h"
35 :
36 108 : PG_MODULE_MAGIC;
37 :
38 : /* Maximum number of waits usable in injection points at once */
39 : #define INJ_MAX_WAIT 8
40 : #define INJ_NAME_MAXLEN 64
41 :
42 : /*
43 : * Conditions related to injection points. This tracks in shared memory the
44 : * runtime conditions under which an injection point is allowed to run,
45 : * stored as private_data when an injection point is attached, and passed as
46 : * argument to the callback.
47 : *
48 : * If more types of runtime conditions need to be tracked, this structure
49 : * should be expanded.
50 : */
51 : typedef enum InjectionPointConditionType
52 : {
53 : INJ_CONDITION_ALWAYS = 0, /* always run */
54 : INJ_CONDITION_PID, /* PID restriction */
55 : } InjectionPointConditionType;
56 :
57 : typedef struct InjectionPointCondition
58 : {
59 : /* Type of the condition */
60 : InjectionPointConditionType type;
61 :
62 : /* ID of the process where the injection point is allowed to run */
63 : int pid;
64 : } InjectionPointCondition;
65 :
66 : /*
67 : * List of injection points stored in TopMemoryContext attached
68 : * locally to this process.
69 : */
70 : static List *inj_list_local = NIL;
71 :
72 : /*
73 : * Shared state information for injection points.
74 : *
75 : * This state data can be initialized in two ways: dynamically with a DSM
76 : * or when loading the module.
77 : */
78 : typedef struct InjectionPointSharedState
79 : {
80 : /* Protects access to other fields */
81 : slock_t lock;
82 :
83 : /* Counters advancing when injection_points_wakeup() is called */
84 : uint32 wait_counts[INJ_MAX_WAIT];
85 :
86 : /* Names of injection points attached to wait counters */
87 : char name[INJ_MAX_WAIT][INJ_NAME_MAXLEN];
88 :
89 : /* Condition variable used for waits and wakeups */
90 : ConditionVariable wait_point;
91 : } InjectionPointSharedState;
92 :
93 : /* Pointer to shared-memory state. */
94 : static InjectionPointSharedState *inj_state = NULL;
95 :
96 : extern PGDLLEXPORT void injection_error(const char *name,
97 : const void *private_data,
98 : void *arg);
99 : extern PGDLLEXPORT void injection_notice(const char *name,
100 : const void *private_data,
101 : void *arg);
102 : extern PGDLLEXPORT void injection_wait(const char *name,
103 : const void *private_data,
104 : void *arg);
105 :
106 : /* track if injection points attached in this process are linked to it */
107 : static bool injection_point_local = false;
108 :
109 : /*
110 : * GUC variable
111 : *
112 : * This GUC is useful to control if statistics should be enabled or not
113 : * during a test with injection points, like for example if a test relies
114 : * on a callback run in a critical section where no allocation should happen.
115 : */
116 : bool inj_stats_enabled = false;
117 :
118 : /* Shared memory init callbacks */
119 : static shmem_request_hook_type prev_shmem_request_hook = NULL;
120 : static shmem_startup_hook_type prev_shmem_startup_hook = NULL;
121 :
122 : /*
123 : * Routine for shared memory area initialization, used as a callback
124 : * when initializing dynamically with a DSM or when loading the module.
125 : */
126 : static void
127 20 : injection_point_init_state(void *ptr)
128 : {
129 20 : InjectionPointSharedState *state = (InjectionPointSharedState *) ptr;
130 :
131 20 : SpinLockInit(&state->lock);
132 20 : memset(state->wait_counts, 0, sizeof(state->wait_counts));
133 20 : memset(state->name, 0, sizeof(state->name));
134 20 : ConditionVariableInit(&state->wait_point);
135 20 : }
136 :
137 : /* Shared memory initialization when loading module */
138 : static void
139 8 : injection_shmem_request(void)
140 : {
141 : Size size;
142 :
143 8 : if (prev_shmem_request_hook)
144 2 : prev_shmem_request_hook();
145 :
146 8 : size = MAXALIGN(sizeof(InjectionPointSharedState));
147 8 : RequestAddinShmemSpace(size);
148 8 : }
149 :
150 : static void
151 8 : injection_shmem_startup(void)
152 : {
153 : bool found;
154 :
155 8 : if (prev_shmem_startup_hook)
156 2 : prev_shmem_startup_hook();
157 :
158 : /* Create or attach to the shared memory state */
159 8 : LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
160 :
161 8 : inj_state = ShmemInitStruct("injection_points",
162 : sizeof(InjectionPointSharedState),
163 : &found);
164 :
165 8 : if (!found)
166 : {
167 : /*
168 : * First time through, so initialize. This is shared with the dynamic
169 : * initialization using a DSM.
170 : */
171 8 : injection_point_init_state(inj_state);
172 : }
173 :
174 8 : LWLockRelease(AddinShmemInitLock);
175 8 : }
176 :
177 : /*
178 : * Initialize shared memory area for this module through DSM.
179 : */
180 : static void
181 36 : injection_init_shmem(void)
182 : {
183 : bool found;
184 :
185 36 : if (inj_state != NULL)
186 0 : return;
187 :
188 36 : inj_state = GetNamedDSMSegment("injection_points",
189 : sizeof(InjectionPointSharedState),
190 : injection_point_init_state,
191 : &found);
192 : }
193 :
194 : /*
195 : * Check runtime conditions associated to an injection point.
196 : *
197 : * Returns true if the named injection point is allowed to run, and false
198 : * otherwise.
199 : */
200 : static bool
201 228 : injection_point_allowed(InjectionPointCondition *condition)
202 : {
203 228 : bool result = true;
204 :
205 228 : switch (condition->type)
206 : {
207 170 : case INJ_CONDITION_PID:
208 170 : if (MyProcPid != condition->pid)
209 78 : result = false;
210 170 : break;
211 58 : case INJ_CONDITION_ALWAYS:
212 58 : break;
213 : }
214 :
215 228 : return result;
216 : }
217 :
218 : /*
219 : * before_shmem_exit callback to remove injection points linked to a
220 : * specific process.
221 : */
222 : static void
223 42 : injection_points_cleanup(int code, Datum arg)
224 : {
225 : ListCell *lc;
226 :
227 : /* Leave if nothing is tracked locally */
228 42 : if (!injection_point_local)
229 0 : return;
230 :
231 : /* Detach all the local points */
232 206 : foreach(lc, inj_list_local)
233 : {
234 164 : char *name = strVal(lfirst(lc));
235 :
236 164 : (void) InjectionPointDetach(name);
237 :
238 : /* Remove stats entry */
239 164 : pgstat_drop_inj(name);
240 : }
241 : }
242 :
243 : /* Set of callbacks available to be attached to an injection point. */
244 : void
245 18 : injection_error(const char *name, const void *private_data, void *arg)
246 : {
247 18 : InjectionPointCondition *condition = (InjectionPointCondition *) private_data;
248 18 : char *argstr = (char *) arg;
249 :
250 18 : if (!injection_point_allowed(condition))
251 0 : return;
252 :
253 18 : pgstat_report_inj(name);
254 :
255 18 : if (argstr)
256 2 : elog(ERROR, "error triggered for injection point %s (%s)",
257 : name, argstr);
258 : else
259 16 : elog(ERROR, "error triggered for injection point %s", name);
260 : }
261 :
262 : void
263 90 : injection_notice(const char *name, const void *private_data, void *arg)
264 : {
265 90 : InjectionPointCondition *condition = (InjectionPointCondition *) private_data;
266 90 : char *argstr = (char *) arg;
267 :
268 90 : if (!injection_point_allowed(condition))
269 0 : return;
270 :
271 90 : pgstat_report_inj(name);
272 :
273 90 : if (argstr)
274 4 : elog(NOTICE, "notice triggered for injection point %s (%s)",
275 : name, argstr);
276 : else
277 86 : elog(NOTICE, "notice triggered for injection point %s", name);
278 : }
279 :
280 : /* Wait on a condition variable, awaken by injection_points_wakeup() */
281 : void
282 120 : injection_wait(const char *name, const void *private_data, void *arg)
283 : {
284 120 : uint32 old_wait_counts = 0;
285 120 : int index = -1;
286 120 : uint32 injection_wait_event = 0;
287 120 : InjectionPointCondition *condition = (InjectionPointCondition *) private_data;
288 :
289 120 : if (inj_state == NULL)
290 10 : injection_init_shmem();
291 :
292 120 : if (!injection_point_allowed(condition))
293 78 : return;
294 :
295 42 : pgstat_report_inj(name);
296 :
297 : /*
298 : * Use the injection point name for this custom wait event. Note that
299 : * this custom wait event name is not released, but we don't care much for
300 : * testing as this should be short-lived.
301 : */
302 42 : injection_wait_event = WaitEventInjectionPointNew(name);
303 :
304 : /*
305 : * Find a free slot to wait for, and register this injection point's name.
306 : */
307 42 : SpinLockAcquire(&inj_state->lock);
308 50 : for (int i = 0; i < INJ_MAX_WAIT; i++)
309 : {
310 50 : if (inj_state->name[i][0] == '\0')
311 : {
312 42 : index = i;
313 42 : strlcpy(inj_state->name[i], name, INJ_NAME_MAXLEN);
314 42 : old_wait_counts = inj_state->wait_counts[i];
315 42 : break;
316 : }
317 : }
318 42 : SpinLockRelease(&inj_state->lock);
319 :
320 42 : if (index < 0)
321 0 : elog(ERROR, "could not find free slot for wait of injection point %s ",
322 : name);
323 :
324 : /* And sleep.. */
325 42 : ConditionVariablePrepareToSleep(&inj_state->wait_point);
326 : for (;;)
327 50 : {
328 : uint32 new_wait_counts;
329 :
330 92 : SpinLockAcquire(&inj_state->lock);
331 92 : new_wait_counts = inj_state->wait_counts[index];
332 92 : SpinLockRelease(&inj_state->lock);
333 :
334 92 : if (old_wait_counts != new_wait_counts)
335 40 : break;
336 52 : ConditionVariableSleep(&inj_state->wait_point, injection_wait_event);
337 : }
338 40 : ConditionVariableCancelSleep();
339 :
340 : /* Remove this injection point from the waiters. */
341 40 : SpinLockAcquire(&inj_state->lock);
342 40 : inj_state->name[index][0] = '\0';
343 40 : SpinLockRelease(&inj_state->lock);
344 : }
345 :
346 : /*
347 : * SQL function for creating an injection point.
348 : */
349 140 : PG_FUNCTION_INFO_V1(injection_points_attach);
350 : Datum
351 108 : injection_points_attach(PG_FUNCTION_ARGS)
352 : {
353 108 : char *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
354 108 : char *action = text_to_cstring(PG_GETARG_TEXT_PP(1));
355 : char *function;
356 108 : InjectionPointCondition condition = {0};
357 :
358 108 : if (strcmp(action, "error") == 0)
359 32 : function = "injection_error";
360 76 : else if (strcmp(action, "notice") == 0)
361 26 : function = "injection_notice";
362 50 : else if (strcmp(action, "wait") == 0)
363 48 : function = "injection_wait";
364 : else
365 2 : elog(ERROR, "incorrect action \"%s\" for injection point creation", action);
366 :
367 106 : if (injection_point_local)
368 : {
369 56 : condition.type = INJ_CONDITION_PID;
370 56 : condition.pid = MyProcPid;
371 : }
372 :
373 106 : pgstat_report_inj_fixed(1, 0, 0, 0, 0);
374 106 : InjectionPointAttach(name, "injection_points", function, &condition,
375 : sizeof(InjectionPointCondition));
376 :
377 106 : if (injection_point_local)
378 : {
379 : MemoryContext oldctx;
380 :
381 : /* Local injection point, so track it for automated cleanup */
382 56 : oldctx = MemoryContextSwitchTo(TopMemoryContext);
383 56 : inj_list_local = lappend(inj_list_local, makeString(pstrdup(name)));
384 56 : MemoryContextSwitchTo(oldctx);
385 : }
386 :
387 : /* Add entry for stats */
388 106 : pgstat_create_inj(name);
389 :
390 106 : PG_RETURN_VOID();
391 : }
392 :
393 : /*
394 : * SQL function for loading an injection point.
395 : */
396 60 : PG_FUNCTION_INFO_V1(injection_points_load);
397 : Datum
398 6 : injection_points_load(PG_FUNCTION_ARGS)
399 : {
400 6 : char *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
401 :
402 6 : if (inj_state == NULL)
403 2 : injection_init_shmem();
404 :
405 6 : pgstat_report_inj_fixed(0, 0, 0, 0, 1);
406 6 : INJECTION_POINT_LOAD(name);
407 :
408 6 : PG_RETURN_VOID();
409 : }
410 :
411 : /*
412 : * SQL function for triggering an injection point.
413 : */
414 76 : PG_FUNCTION_INFO_V1(injection_points_run);
415 : Datum
416 62 : injection_points_run(PG_FUNCTION_ARGS)
417 : {
418 : char *name;
419 62 : char *arg = NULL;
420 :
421 62 : if (PG_ARGISNULL(0))
422 2 : PG_RETURN_VOID();
423 60 : name = text_to_cstring(PG_GETARG_TEXT_PP(0));
424 :
425 60 : if (!PG_ARGISNULL(1))
426 4 : arg = text_to_cstring(PG_GETARG_TEXT_PP(1));
427 :
428 60 : pgstat_report_inj_fixed(0, 0, 1, 0, 0);
429 60 : INJECTION_POINT(name, arg);
430 :
431 48 : PG_RETURN_VOID();
432 : }
433 :
434 : /*
435 : * SQL function for triggering an injection point from cache.
436 : */
437 62 : PG_FUNCTION_INFO_V1(injection_points_cached);
438 : Datum
439 12 : injection_points_cached(PG_FUNCTION_ARGS)
440 : {
441 : char *name;
442 12 : char *arg = NULL;
443 :
444 12 : if (PG_ARGISNULL(0))
445 2 : PG_RETURN_VOID();
446 10 : name = text_to_cstring(PG_GETARG_TEXT_PP(0));
447 :
448 10 : if (!PG_ARGISNULL(1))
449 2 : arg = text_to_cstring(PG_GETARG_TEXT_PP(1));
450 :
451 10 : pgstat_report_inj_fixed(0, 0, 0, 1, 0);
452 10 : INJECTION_POINT_CACHED(name, arg);
453 :
454 10 : PG_RETURN_VOID();
455 : }
456 :
457 : /*
458 : * SQL function for waking up an injection point waiting in injection_wait().
459 : */
460 94 : PG_FUNCTION_INFO_V1(injection_points_wakeup);
461 : Datum
462 44 : injection_points_wakeup(PG_FUNCTION_ARGS)
463 : {
464 44 : char *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
465 44 : int index = -1;
466 :
467 44 : if (inj_state == NULL)
468 8 : injection_init_shmem();
469 :
470 : /* First bump the wait counter for the injection point to wake up */
471 44 : SpinLockAcquire(&inj_state->lock);
472 68 : for (int i = 0; i < INJ_MAX_WAIT; i++)
473 : {
474 66 : if (strcmp(name, inj_state->name[i]) == 0)
475 : {
476 42 : index = i;
477 42 : break;
478 : }
479 : }
480 44 : if (index < 0)
481 : {
482 2 : SpinLockRelease(&inj_state->lock);
483 2 : elog(ERROR, "could not find injection point %s to wake up", name);
484 : }
485 42 : inj_state->wait_counts[index]++;
486 42 : SpinLockRelease(&inj_state->lock);
487 :
488 : /* And broadcast the change to the waiters */
489 42 : ConditionVariableBroadcast(&inj_state->wait_point);
490 42 : PG_RETURN_VOID();
491 : }
492 :
493 : /*
494 : * injection_points_set_local
495 : *
496 : * Track if any injection point created in this process ought to run only
497 : * in this process. Such injection points are detached automatically when
498 : * this process exits. This is useful to make test suites concurrent-safe.
499 : */
500 98 : PG_FUNCTION_INFO_V1(injection_points_set_local);
501 : Datum
502 42 : injection_points_set_local(PG_FUNCTION_ARGS)
503 : {
504 : /* Enable flag to add a runtime condition based on this process ID */
505 42 : injection_point_local = true;
506 :
507 42 : if (inj_state == NULL)
508 16 : injection_init_shmem();
509 :
510 : /*
511 : * Register a before_shmem_exit callback to remove any injection points
512 : * linked to this process.
513 : */
514 42 : before_shmem_exit(injection_points_cleanup, (Datum) 0);
515 :
516 42 : PG_RETURN_VOID();
517 : }
518 :
519 : /*
520 : * SQL function for dropping an injection point.
521 : */
522 108 : PG_FUNCTION_INFO_V1(injection_points_detach);
523 : Datum
524 74 : injection_points_detach(PG_FUNCTION_ARGS)
525 : {
526 74 : char *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
527 :
528 74 : pgstat_report_inj_fixed(0, 1, 0, 0, 0);
529 74 : if (!InjectionPointDetach(name))
530 2 : elog(ERROR, "could not detach injection point \"%s\"", name);
531 :
532 : /* Remove point from local list, if required */
533 72 : if (inj_list_local != NIL)
534 : {
535 : MemoryContext oldctx;
536 :
537 12 : oldctx = MemoryContextSwitchTo(TopMemoryContext);
538 12 : inj_list_local = list_delete(inj_list_local, makeString(name));
539 12 : MemoryContextSwitchTo(oldctx);
540 : }
541 :
542 : /* Remove stats entry */
543 72 : pgstat_drop_inj(name);
544 :
545 72 : PG_RETURN_VOID();
546 : }
547 :
548 :
549 : void
550 108 : _PG_init(void)
551 : {
552 108 : if (!process_shared_preload_libraries_in_progress)
553 100 : return;
554 :
555 8 : DefineCustomBoolVariable("injection_points.stats",
556 : "Enables statistics for injection points.",
557 : NULL,
558 : &inj_stats_enabled,
559 : false,
560 : PGC_POSTMASTER,
561 : 0,
562 : NULL,
563 : NULL,
564 : NULL);
565 :
566 8 : MarkGUCPrefixReserved("injection_points");
567 :
568 : /* Shared memory initialization */
569 8 : prev_shmem_request_hook = shmem_request_hook;
570 8 : shmem_request_hook = injection_shmem_request;
571 8 : prev_shmem_startup_hook = shmem_startup_hook;
572 8 : shmem_startup_hook = injection_shmem_startup;
573 :
574 8 : pgstat_register_inj();
575 8 : pgstat_register_inj_fixed();
576 : }
|