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

Generated by: LCOV version 2.0-1