LCOV - code coverage report
Current view: top level - src/test/modules/injection_points - injection_points.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 97.7 % 215 210
Test Date: 2026-03-24 10:16:09 Functions: 100.0 % 29 29
Legend: Lines:     hit not hit

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

Generated by: LCOV version 2.0-1