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