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

Generated by: LCOV version 2.0-1