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