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