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