Age Owner Branch data TLA 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 : :
859 michael@paquier.xyz 38 :CBC 187 : 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
166 nathan@postgresql.or 100 :GNC 15 : injection_point_init_state(void *ptr, void *arg)
101 : : {
817 michael@paquier.xyz 102 :CBC 15 : InjectionPointSharedState *state = (InjectionPointSharedState *) ptr;
103 : :
104 : 15 : SpinLockInit(&state->lock);
105 : 15 : memset(state->wait_counts, 0, sizeof(state->wait_counts));
106 : 15 : memset(state->name, 0, sizeof(state->name));
107 : 15 : ConditionVariableInit(&state->wait_point);
108 : 15 : }
109 : :
110 : : static void
54 heikki.linnakangas@i 111 :GNC 3 : injection_shmem_request(void *arg)
112 : : {
113 : 3 : ShmemRequestStruct(.name = "injection_points",
114 : : .size = sizeof(InjectionPointSharedState),
115 : : .ptr = (void **) &inj_state,
116 : : );
645 michael@paquier.xyz 117 :CBC 3 : }
118 : :
119 : : static void
54 heikki.linnakangas@i 120 :GNC 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);
645 michael@paquier.xyz 127 :CBC 3 : }
128 : :
129 : : /*
130 : : * Initialize shared memory area for this module through DSM.
131 : : */
132 : : static void
817 133 : 122 : injection_init_shmem(void)
134 : : {
135 : : bool found;
136 : :
137 [ - + ]: 122 : if (inj_state != NULL)
817 michael@paquier.xyz 138 :UBC 0 : return;
139 : :
817 michael@paquier.xyz 140 :CBC 122 : 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
138 peter@eisentraut.org 153 :GNC 262 : injection_point_allowed(const InjectionPointCondition *condition)
154 : : {
782 michael@paquier.xyz 155 :CBC 262 : bool result = true;
156 : :
748 157 [ + + - ]: 262 : switch (condition->type)
158 : : {
159 : 232 : case INJ_CONDITION_PID:
782 160 [ + + ]: 232 : if (MyProcPid != condition->pid)
161 : 124 : result = false;
748 162 : 232 : break;
163 : 30 : case INJ_CONDITION_ALWAYS:
164 : 30 : break;
165 : : }
166 : :
782 167 : 262 : 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 : 63 : injection_points_cleanup(int code, Datum arg)
176 : : {
177 : : ListCell *lc;
178 : :
179 : : /* Leave if nothing is tracked locally */
180 [ - + ]: 63 : if (!injection_point_local)
782 michael@paquier.xyz 181 :UBC 0 : return;
182 : :
183 : : /* Detach all the local points */
748 michael@paquier.xyz 184 [ + + + + :CBC 184 : foreach(lc, inj_list_local)
+ + ]
185 : : {
186 : 121 : char *name = strVal(lfirst(lc));
187 : :
188 : 121 : (void) InjectionPointDetach(name);
189 : : }
190 : : }
191 : :
192 : : /* Set of callbacks available to be attached to an injection point. */
193 : : void
385 194 : 13 : injection_error(const char *name, const void *private_data, void *arg)
195 : : {
138 peter@eisentraut.org 196 :GNC 13 : const InjectionPointCondition *condition = private_data;
197 : 13 : char *argstr = arg;
198 : :
748 michael@paquier.xyz 199 [ - + ]:CBC 13 : if (!injection_point_allowed(condition))
782 michael@paquier.xyz 200 :UBC 0 : return;
201 : :
385 michael@paquier.xyz 202 [ + + ]:CBC 13 : if (argstr)
203 [ + - ]: 1 : elog(ERROR, "error triggered for injection point %s (%s)",
204 : : name, argstr);
205 : : else
206 [ + - ]: 12 : elog(ERROR, "error triggered for injection point %s", name);
207 : : }
208 : :
209 : : void
210 : 61 : injection_notice(const char *name, const void *private_data, void *arg)
211 : : {
138 peter@eisentraut.org 212 :GNC 61 : const InjectionPointCondition *condition = private_data;
213 : 61 : char *argstr = arg;
214 : :
748 michael@paquier.xyz 215 [ - + ]:CBC 61 : if (!injection_point_allowed(condition))
782 michael@paquier.xyz 216 :UBC 0 : return;
217 : :
385 michael@paquier.xyz 218 [ + + ]:CBC 61 : if (argstr)
219 [ + + ]: 3 : elog(NOTICE, "notice triggered for injection point %s (%s)",
220 : : name, argstr);
221 : : else
222 [ + - ]: 58 : 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 : 188 : injection_wait(const char *name, const void *private_data, void *arg)
228 : : {
817 229 : 188 : uint32 old_wait_counts = 0;
230 : 188 : int index = -1;
231 : 188 : uint32 injection_wait_event = 0;
138 peter@eisentraut.org 232 :GNC 188 : const InjectionPointCondition *condition = private_data;
233 : :
817 michael@paquier.xyz 234 [ + + ]:CBC 188 : if (inj_state == NULL)
235 : 24 : injection_init_shmem();
236 : :
748 237 [ + + ]: 188 : if (!injection_point_allowed(condition))
782 238 : 124 : 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 : : */
702 noah@leadboat.com 245 : 64 : injection_wait_event = WaitEventInjectionPointNew(name);
246 : :
247 : : /*
248 : : * Find a free slot to wait for, and register this injection point's name.
249 : : */
817 michael@paquier.xyz 250 [ - + ]: 64 : SpinLockAcquire(&inj_state->lock);
251 [ + - ]: 85 : for (int i = 0; i < INJ_MAX_WAIT; i++)
252 : : {
253 [ + + ]: 85 : if (inj_state->name[i][0] == '\0')
254 : : {
255 : 64 : index = i;
256 : 64 : strlcpy(inj_state->name[i], name, INJ_NAME_MAXLEN);
257 : 64 : old_wait_counts = inj_state->wait_counts[i];
258 : 64 : break;
259 : : }
260 : : }
261 : 64 : SpinLockRelease(&inj_state->lock);
262 : :
263 [ - + ]: 64 : if (index < 0)
817 michael@paquier.xyz 264 [ # # ]:UBC 0 : elog(ERROR, "could not find free slot for wait of injection point %s ",
265 : : name);
266 : :
267 : : /* And sleep.. */
817 michael@paquier.xyz 268 :CBC 64 : ConditionVariablePrepareToSleep(&inj_state->wait_point);
269 : : for (;;)
270 : 90 : {
271 : : uint32 new_wait_counts;
272 : :
273 [ - + ]: 154 : SpinLockAcquire(&inj_state->lock);
274 : 154 : new_wait_counts = inj_state->wait_counts[index];
275 : 154 : SpinLockRelease(&inj_state->lock);
276 : :
277 [ + + ]: 154 : if (old_wait_counts != new_wait_counts)
278 : 62 : break;
279 : 92 : ConditionVariableSleep(&inj_state->wait_point, injection_wait_event);
280 : : }
281 : 62 : ConditionVariableCancelSleep();
282 : :
283 : : /* Remove this injection point from the waiters. */
284 [ - + ]: 62 : SpinLockAcquire(&inj_state->lock);
285 : 62 : inj_state->name[index][0] = '\0';
286 : 62 : SpinLockRelease(&inj_state->lock);
287 : : }
288 : :
289 : : /*
290 : : * SQL function for creating an injection point.
291 : : */
859 292 : 140 : PG_FUNCTION_INFO_V1(injection_points_attach);
293 : : Datum
294 : 115 : injection_points_attach(PG_FUNCTION_ARGS)
295 : : {
296 : 115 : char *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
297 : 115 : char *action = text_to_cstring(PG_GETARG_TEXT_PP(1));
298 : : char *function;
748 299 : 115 : InjectionPointCondition condition = {0};
300 : :
859 301 [ + + ]: 115 : if (strcmp(action, "error") == 0)
302 : 23 : function = "injection_error";
303 [ + + ]: 92 : else if (strcmp(action, "notice") == 0)
304 : 20 : function = "injection_notice";
817 305 [ + + ]: 72 : else if (strcmp(action, "wait") == 0)
306 : 71 : function = "injection_wait";
307 : : else
859 308 [ + - ]: 1 : elog(ERROR, "incorrect action \"%s\" for injection point creation", action);
309 : :
782 310 [ + + ]: 114 : if (injection_point_local)
311 : : {
748 312 : 79 : condition.type = INJ_CONDITION_PID;
313 : 79 : condition.pid = MyProcPid;
314 : : }
315 : :
316 : 114 : InjectionPointAttach(name, "injection_points", function, &condition,
317 : : sizeof(InjectionPointCondition));
318 : :
319 [ + + ]: 114 : if (injection_point_local)
320 : : {
321 : : MemoryContext oldctx;
322 : :
323 : : /* Local injection point, so track it for automated cleanup */
324 : 79 : oldctx = MemoryContextSwitchTo(TopMemoryContext);
325 : 79 : inj_list_local = lappend(inj_list_local, makeString(pstrdup(name)));
326 : 79 : MemoryContextSwitchTo(oldctx);
327 : : }
328 : :
859 michael@paquier.xyz 329 :GNC 114 : PG_RETURN_VOID();
330 : : }
331 : :
332 : : /*
333 : : * SQL function for creating an injection point with library name, function
334 : : * name and private data.
335 : : */
201 336 : 47 : 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);
201 michael@paquier.xyz 369 :CBC 1 : PG_RETURN_VOID();
370 : : }
371 : :
372 : : /*
373 : : * SQL function for loading an injection point.
374 : : */
694 375 : 47 : 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 : : */
859 392 : 52 : PG_FUNCTION_INFO_V1(injection_points_run);
393 : : Datum
394 : 28 : injection_points_run(PG_FUNCTION_ARGS)
395 : : {
396 : : char *name;
385 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 : :
859 408 : 21 : PG_RETURN_VOID();
409 : : }
410 : :
411 : : /*
412 : : * SQL function for triggering an injection point from cache.
413 : : */
681 414 : 48 : PG_FUNCTION_INFO_V1(injection_points_cached);
415 : : Datum
416 : 5 : injection_points_cached(PG_FUNCTION_ARGS)
417 : : {
418 : : char *name;
385 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 : :
681 430 : 4 : PG_RETURN_VOID();
431 : : }
432 : :
433 : : /*
434 : : * SQL function for waking up an injection point waiting in injection_wait().
435 : : */
817 436 : 114 : PG_FUNCTION_INFO_V1(injection_points_wakeup);
437 : : Datum
438 : 71 : injection_points_wakeup(PG_FUNCTION_ARGS)
439 : : {
440 : 71 : char *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
441 : 71 : int index = -1;
442 : :
443 [ + + ]: 71 : if (inj_state == NULL)
444 : 50 : injection_init_shmem();
445 : :
446 : : /* First bump the wait counter for the injection point to wake up */
447 [ - + ]: 71 : SpinLockAcquire(&inj_state->lock);
448 [ + + ]: 102 : for (int i = 0; i < INJ_MAX_WAIT; i++)
449 : : {
450 [ + + ]: 101 : if (strcmp(name, inj_state->name[i]) == 0)
451 : : {
452 : 70 : index = i;
453 : 70 : break;
454 : : }
455 : : }
456 [ + + ]: 71 : 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 : 70 : inj_state->wait_counts[index]++;
462 : 70 : SpinLockRelease(&inj_state->lock);
463 : :
464 : : /* And broadcast the change to the waiters */
465 : 70 : ConditionVariableBroadcast(&inj_state->wait_point);
466 : 70 : 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 : : */
782 476 : 109 : PG_FUNCTION_INFO_V1(injection_points_set_local);
477 : : Datum
478 : 63 : injection_points_set_local(PG_FUNCTION_ARGS)
479 : : {
480 : : /* Enable flag to add a runtime condition based on this process ID */
481 : 63 : injection_point_local = true;
482 : :
483 [ + + ]: 63 : if (inj_state == NULL)
484 : 47 : injection_init_shmem();
485 : :
486 : : /*
487 : : * Register a before_shmem_exit callback to remove any injection points
488 : : * linked to this process.
489 : : */
490 : 63 : before_shmem_exit(injection_points_cleanup, (Datum) 0);
491 : :
492 : 63 : PG_RETURN_VOID();
493 : : }
494 : :
495 : : /*
496 : : * SQL function for dropping an injection point.
497 : : */
859 498 : 124 : PG_FUNCTION_INFO_V1(injection_points_detach);
499 : : Datum
500 : 97 : injection_points_detach(PG_FUNCTION_ARGS)
501 : : {
502 : 97 : char *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
503 : :
748 504 [ + + ]: 97 : if (!InjectionPointDetach(name))
505 [ + - ]: 1 : elog(ERROR, "could not detach injection point \"%s\"", name);
506 : :
507 : : /* Remove point from local list, if required */
508 [ + + ]: 96 : if (inj_list_local != NIL)
509 : : {
510 : : MemoryContext oldctx;
511 : :
512 : 20 : oldctx = MemoryContextSwitchTo(TopMemoryContext);
513 : 20 : inj_list_local = list_delete(inj_list_local, makeString(name));
514 : 20 : MemoryContextSwitchTo(oldctx);
515 : : }
516 : :
859 517 : 96 : PG_RETURN_VOID();
518 : : }
519 : :
520 : : /*
521 : : * SQL function for listing all the injection points attached.
522 : : */
324 michael@paquier.xyz 523 :GNC 48 : 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
663 michael@paquier.xyz 559 :CBC 187 : _PG_init(void)
560 : : {
561 [ + + ]: 187 : if (!process_shared_preload_libraries_in_progress)
562 : 184 : return;
563 : :
54 heikki.linnakangas@i 564 :GNC 3 : RegisterShmemCallbacks(&injection_shmem_callbacks);
565 : : }
|