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