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

Generated by: LCOV version 2.0-1