LCOV - code coverage report
Current view: top level - src/test/modules/injection_points - injection_points.c (source / functions) Hit Total Coverage
Test: PostgreSQL 19devel Lines: 210 215 97.7 %
Date: 2025-12-09 01:18:30 Functions: 29 29 100.0 %
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-2025, 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         160 : 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          16 : injection_point_init_state(void *ptr)
     119             : {
     120          16 :     InjectionPointSharedState *state = (InjectionPointSharedState *) ptr;
     121             : 
     122          16 :     SpinLockInit(&state->lock);
     123          16 :     memset(state->wait_counts, 0, sizeof(state->wait_counts));
     124          16 :     memset(state->name, 0, sizeof(state->name));
     125          16 :     ConditionVariableInit(&state->wait_point);
     126          16 : }
     127             : 
     128             : /* Shared memory initialization when loading module */
     129             : static void
     130           6 : injection_shmem_request(void)
     131             : {
     132             :     Size        size;
     133             : 
     134           6 :     if (prev_shmem_request_hook)
     135           4 :         prev_shmem_request_hook();
     136             : 
     137           6 :     size = MAXALIGN(sizeof(InjectionPointSharedState));
     138           6 :     RequestAddinShmemSpace(size);
     139           6 : }
     140             : 
     141             : static void
     142           6 : injection_shmem_startup(void)
     143             : {
     144             :     bool        found;
     145             : 
     146           6 :     if (prev_shmem_startup_hook)
     147           4 :         prev_shmem_startup_hook();
     148             : 
     149             :     /* Create or attach to the shared memory state */
     150           6 :     LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
     151             : 
     152           6 :     inj_state = ShmemInitStruct("injection_points",
     153             :                                 sizeof(InjectionPointSharedState),
     154             :                                 &found);
     155             : 
     156           6 :     if (!found)
     157             :     {
     158             :         /*
     159             :          * First time through, so initialize.  This is shared with the dynamic
     160             :          * initialization using a DSM.
     161             :          */
     162           6 :         injection_point_init_state(inj_state);
     163             :     }
     164             : 
     165           6 :     LWLockRelease(AddinShmemInitLock);
     166           6 : }
     167             : 
     168             : /*
     169             :  * Initialize shared memory area for this module through DSM.
     170             :  */
     171             : static void
     172          80 : injection_init_shmem(void)
     173             : {
     174             :     bool        found;
     175             : 
     176          80 :     if (inj_state != NULL)
     177           0 :         return;
     178             : 
     179          80 :     inj_state = GetNamedDSMSegment("injection_points",
     180             :                                    sizeof(InjectionPointSharedState),
     181             :                                    injection_point_init_state,
     182             :                                    &found);
     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         436 : injection_point_allowed(InjectionPointCondition *condition)
     193             : {
     194         436 :     bool        result = true;
     195             : 
     196         436 :     switch (condition->type)
     197             :     {
     198         392 :         case INJ_CONDITION_PID:
     199         392 :             if (MyProcPid != condition->pid)
     200         198 :                 result = false;
     201         392 :             break;
     202          44 :         case INJ_CONDITION_ALWAYS:
     203          44 :             break;
     204             :     }
     205             : 
     206         436 :     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         110 : injection_points_cleanup(int code, Datum arg)
     215             : {
     216             :     ListCell   *lc;
     217             : 
     218             :     /* Leave if nothing is tracked locally */
     219         110 :     if (!injection_point_local)
     220           0 :         return;
     221             : 
     222             :     /* Detach all the local points */
     223         452 :     foreach(lc, inj_list_local)
     224             :     {
     225         342 :         char       *name = strVal(lfirst(lc));
     226             : 
     227         342 :         (void) InjectionPointDetach(name);
     228             :     }
     229             : }
     230             : 
     231             : /* Set of callbacks available to be attached to an injection point. */
     232             : void
     233          18 : injection_error(const char *name, const void *private_data, void *arg)
     234             : {
     235          18 :     InjectionPointCondition *condition = (InjectionPointCondition *) private_data;
     236          18 :     char       *argstr = (char *) arg;
     237             : 
     238          18 :     if (!injection_point_allowed(condition))
     239           0 :         return;
     240             : 
     241          18 :     if (argstr)
     242           2 :         elog(ERROR, "error triggered for injection point %s (%s)",
     243             :              name, argstr);
     244             :     else
     245          16 :         elog(ERROR, "error triggered for injection point %s", name);
     246             : }
     247             : 
     248             : void
     249         114 : injection_notice(const char *name, const void *private_data, void *arg)
     250             : {
     251         114 :     InjectionPointCondition *condition = (InjectionPointCondition *) private_data;
     252         114 :     char       *argstr = (char *) arg;
     253             : 
     254         114 :     if (!injection_point_allowed(condition))
     255           0 :         return;
     256             : 
     257         114 :     if (argstr)
     258           4 :         elog(NOTICE, "notice triggered for injection point %s (%s)",
     259             :              name, argstr);
     260             :     else
     261         110 :         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         304 : injection_wait(const char *name, const void *private_data, void *arg)
     267             : {
     268         304 :     uint32      old_wait_counts = 0;
     269         304 :     int         index = -1;
     270         304 :     uint32      injection_wait_event = 0;
     271         304 :     InjectionPointCondition *condition = (InjectionPointCondition *) private_data;
     272             : 
     273         304 :     if (inj_state == NULL)
     274          20 :         injection_init_shmem();
     275             : 
     276         304 :     if (!injection_point_allowed(condition))
     277         198 :         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         106 :     injection_wait_event = WaitEventInjectionPointNew(name);
     285             : 
     286             :     /*
     287             :      * Find a free slot to wait for, and register this injection point's name.
     288             :      */
     289         106 :     SpinLockAcquire(&inj_state->lock);
     290         150 :     for (int i = 0; i < INJ_MAX_WAIT; i++)
     291             :     {
     292         150 :         if (inj_state->name[i][0] == '\0')
     293             :         {
     294         106 :             index = i;
     295         106 :             strlcpy(inj_state->name[i], name, INJ_NAME_MAXLEN);
     296         106 :             old_wait_counts = inj_state->wait_counts[i];
     297         106 :             break;
     298             :         }
     299             :     }
     300         106 :     SpinLockRelease(&inj_state->lock);
     301             : 
     302         106 :     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         106 :     ConditionVariablePrepareToSleep(&inj_state->wait_point);
     308             :     for (;;)
     309         166 :     {
     310             :         uint32      new_wait_counts;
     311             : 
     312         272 :         SpinLockAcquire(&inj_state->lock);
     313         272 :         new_wait_counts = inj_state->wait_counts[index];
     314         272 :         SpinLockRelease(&inj_state->lock);
     315             : 
     316         272 :         if (old_wait_counts != new_wait_counts)
     317         106 :             break;
     318         166 :         ConditionVariableSleep(&inj_state->wait_point, injection_wait_event);
     319             :     }
     320         106 :     ConditionVariableCancelSleep();
     321             : 
     322             :     /* Remove this injection point from the waiters. */
     323         106 :     SpinLockAcquire(&inj_state->lock);
     324         106 :     inj_state->name[index][0] = '\0';
     325         106 :     SpinLockRelease(&inj_state->lock);
     326             : }
     327             : 
     328             : /*
     329             :  * SQL function for creating an injection point.
     330             :  */
     331         228 : PG_FUNCTION_INFO_V1(injection_points_attach);
     332             : Datum
     333         182 : injection_points_attach(PG_FUNCTION_ARGS)
     334             : {
     335         182 :     char       *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
     336         182 :     char       *action = text_to_cstring(PG_GETARG_TEXT_PP(1));
     337             :     char       *function;
     338         182 :     InjectionPointCondition condition = {0};
     339             : 
     340         182 :     if (strcmp(action, "error") == 0)
     341          32 :         function = "injection_error";
     342         150 :     else if (strcmp(action, "notice") == 0)
     343          34 :         function = "injection_notice";
     344         116 :     else if (strcmp(action, "wait") == 0)
     345         114 :         function = "injection_wait";
     346             :     else
     347           2 :         elog(ERROR, "incorrect action \"%s\" for injection point creation", action);
     348             : 
     349         180 :     if (injection_point_local)
     350             :     {
     351         138 :         condition.type = INJ_CONDITION_PID;
     352         138 :         condition.pid = MyProcPid;
     353             :     }
     354             : 
     355         180 :     InjectionPointAttach(name, "injection_points", function, &condition,
     356             :                          sizeof(InjectionPointCondition));
     357             : 
     358         180 :     if (injection_point_local)
     359             :     {
     360             :         MemoryContext oldctx;
     361             : 
     362             :         /* Local injection point, so track it for automated cleanup */
     363         138 :         oldctx = MemoryContextSwitchTo(TopMemoryContext);
     364         138 :         inj_list_local = lappend(inj_list_local, makeString(pstrdup(name)));
     365         138 :         MemoryContextSwitchTo(oldctx);
     366             :     }
     367             : 
     368         180 :     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          86 : PG_FUNCTION_INFO_V1(injection_points_attach_func);
     376             : Datum
     377          16 : injection_points_attach_func(PG_FUNCTION_ARGS)
     378             : {
     379             :     char       *name;
     380             :     char       *lib_name;
     381             :     char       *function;
     382          16 :     bytea      *private_data = NULL;
     383          16 :     int         private_data_size = 0;
     384             : 
     385          16 :     if (PG_ARGISNULL(0))
     386           2 :         elog(ERROR, "injection point name cannot be NULL");
     387          14 :     if (PG_ARGISNULL(1))
     388           2 :         elog(ERROR, "injection point library cannot be NULL");
     389          12 :     if (PG_ARGISNULL(2))
     390           2 :         elog(ERROR, "injection point function cannot be NULL");
     391             : 
     392          10 :     name = text_to_cstring(PG_GETARG_TEXT_PP(0));
     393          10 :     lib_name = text_to_cstring(PG_GETARG_TEXT_PP(1));
     394          10 :     function = text_to_cstring(PG_GETARG_TEXT_PP(2));
     395             : 
     396          10 :     if (!PG_ARGISNULL(3))
     397             :     {
     398           2 :         private_data = PG_GETARG_BYTEA_PP(3);
     399           2 :         private_data_size = VARSIZE_ANY_EXHDR(private_data);
     400             :     }
     401             : 
     402          10 :     if (private_data != NULL)
     403           2 :         InjectionPointAttach(name, lib_name, function, VARDATA_ANY(private_data),
     404             :                              private_data_size);
     405             :     else
     406           8 :         InjectionPointAttach(name, lib_name, function, NULL,
     407             :                              0);
     408           2 :     PG_RETURN_VOID();
     409             : }
     410             : 
     411             : /*
     412             :  * SQL function for loading an injection point.
     413             :  */
     414          86 : PG_FUNCTION_INFO_V1(injection_points_load);
     415             : Datum
     416           4 : injection_points_load(PG_FUNCTION_ARGS)
     417             : {
     418           4 :     char       *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
     419             : 
     420           4 :     if (inj_state == NULL)
     421           2 :         injection_init_shmem();
     422             : 
     423           4 :     INJECTION_POINT_LOAD(name);
     424             : 
     425           4 :     PG_RETURN_VOID();
     426             : }
     427             : 
     428             : /*
     429             :  * SQL function for triggering an injection point.
     430             :  */
     431          96 : PG_FUNCTION_INFO_V1(injection_points_run);
     432             : Datum
     433          56 : injection_points_run(PG_FUNCTION_ARGS)
     434             : {
     435             :     char       *name;
     436          56 :     char       *arg = NULL;
     437             : 
     438          56 :     if (PG_ARGISNULL(0))
     439           2 :         PG_RETURN_VOID();
     440          54 :     name = text_to_cstring(PG_GETARG_TEXT_PP(0));
     441             : 
     442          54 :     if (!PG_ARGISNULL(1))
     443           4 :         arg = text_to_cstring(PG_GETARG_TEXT_PP(1));
     444             : 
     445          54 :     INJECTION_POINT(name, arg);
     446             : 
     447          42 :     PG_RETURN_VOID();
     448             : }
     449             : 
     450             : /*
     451             :  * SQL function for triggering an injection point from cache.
     452             :  */
     453          88 : PG_FUNCTION_INFO_V1(injection_points_cached);
     454             : Datum
     455          10 : injection_points_cached(PG_FUNCTION_ARGS)
     456             : {
     457             :     char       *name;
     458          10 :     char       *arg = NULL;
     459             : 
     460          10 :     if (PG_ARGISNULL(0))
     461           2 :         PG_RETURN_VOID();
     462           8 :     name = text_to_cstring(PG_GETARG_TEXT_PP(0));
     463             : 
     464           8 :     if (!PG_ARGISNULL(1))
     465           2 :         arg = text_to_cstring(PG_GETARG_TEXT_PP(1));
     466             : 
     467           8 :     INJECTION_POINT_CACHED(name, arg);
     468             : 
     469           8 :     PG_RETURN_VOID();
     470             : }
     471             : 
     472             : /*
     473             :  * SQL function for waking up an injection point waiting in injection_wait().
     474             :  */
     475         140 : PG_FUNCTION_INFO_V1(injection_points_wakeup);
     476             : Datum
     477         106 : injection_points_wakeup(PG_FUNCTION_ARGS)
     478             : {
     479         106 :     char       *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
     480         106 :     int         index = -1;
     481             : 
     482         106 :     if (inj_state == NULL)
     483          10 :         injection_init_shmem();
     484             : 
     485             :     /* First bump the wait counter for the injection point to wake up */
     486         106 :     SpinLockAcquire(&inj_state->lock);
     487         166 :     for (int i = 0; i < INJ_MAX_WAIT; i++)
     488             :     {
     489         164 :         if (strcmp(name, inj_state->name[i]) == 0)
     490             :         {
     491         104 :             index = i;
     492         104 :             break;
     493             :         }
     494             :     }
     495         106 :     if (index < 0)
     496             :     {
     497           2 :         SpinLockRelease(&inj_state->lock);
     498           2 :         elog(ERROR, "could not find injection point %s to wake up", name);
     499             :     }
     500         104 :     inj_state->wait_counts[index]++;
     501         104 :     SpinLockRelease(&inj_state->lock);
     502             : 
     503             :     /* And broadcast the change to the waiters */
     504         104 :     ConditionVariableBroadcast(&inj_state->wait_point);
     505         104 :     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         194 : PG_FUNCTION_INFO_V1(injection_points_set_local);
     516             : Datum
     517         110 : injection_points_set_local(PG_FUNCTION_ARGS)
     518             : {
     519             :     /* Enable flag to add a runtime condition based on this process ID */
     520         110 :     injection_point_local = true;
     521             : 
     522         110 :     if (inj_state == NULL)
     523          48 :         injection_init_shmem();
     524             : 
     525             :     /*
     526             :      * Register a before_shmem_exit callback to remove any injection points
     527             :      * linked to this process.
     528             :      */
     529         110 :     before_shmem_exit(injection_points_cleanup, (Datum) 0);
     530             : 
     531         110 :     PG_RETURN_VOID();
     532             : }
     533             : 
     534             : /*
     535             :  * SQL function for dropping an injection point.
     536             :  */
     537         160 : PG_FUNCTION_INFO_V1(injection_points_detach);
     538             : Datum
     539         154 : injection_points_detach(PG_FUNCTION_ARGS)
     540             : {
     541         154 :     char       *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
     542             : 
     543         154 :     if (!InjectionPointDetach(name))
     544           2 :         elog(ERROR, "could not detach injection point \"%s\"", name);
     545             : 
     546             :     /* Remove point from local list, if required */
     547         152 :     if (inj_list_local != NIL)
     548             :     {
     549             :         MemoryContext oldctx;
     550             : 
     551          24 :         oldctx = MemoryContextSwitchTo(TopMemoryContext);
     552          24 :         inj_list_local = list_delete(inj_list_local, makeString(name));
     553          24 :         MemoryContextSwitchTo(oldctx);
     554             :     }
     555             : 
     556         152 :     PG_RETURN_VOID();
     557             : }
     558             : 
     559             : /*
     560             :  * SQL function for listing all the injection points attached.
     561             :  */
     562          88 : PG_FUNCTION_INFO_V1(injection_points_list);
     563             : Datum
     564           6 : injection_points_list(PG_FUNCTION_ARGS)
     565             : {
     566             : #define NUM_INJECTION_POINTS_LIST 3
     567           6 :     ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
     568             :     List       *inj_points;
     569             :     ListCell   *lc;
     570             : 
     571             :     /* Build a tuplestore to return our results in */
     572           6 :     InitMaterializedSRF(fcinfo, 0);
     573             : 
     574           6 :     inj_points = InjectionPointList();
     575             : 
     576          14 :     foreach(lc, inj_points)
     577             :     {
     578             :         Datum       values[NUM_INJECTION_POINTS_LIST];
     579             :         bool        nulls[NUM_INJECTION_POINTS_LIST];
     580           8 :         InjectionPointData *inj_point = lfirst(lc);
     581             : 
     582           8 :         memset(values, 0, sizeof(values));
     583           8 :         memset(nulls, 0, sizeof(nulls));
     584             : 
     585           8 :         values[0] = PointerGetDatum(cstring_to_text(inj_point->name));
     586           8 :         values[1] = PointerGetDatum(cstring_to_text(inj_point->library));
     587           8 :         values[2] = PointerGetDatum(cstring_to_text(inj_point->function));
     588             : 
     589             :         /* shove row into tuplestore */
     590           8 :         tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
     591             :     }
     592             : 
     593           6 :     return (Datum) 0;
     594             : #undef NUM_INJECTION_POINTS_LIST
     595             : }
     596             : 
     597             : void
     598         160 : _PG_init(void)
     599             : {
     600         160 :     if (!process_shared_preload_libraries_in_progress)
     601         154 :         return;
     602             : 
     603             :     /* Shared memory initialization */
     604           6 :     prev_shmem_request_hook = shmem_request_hook;
     605           6 :     shmem_request_hook = injection_shmem_request;
     606           6 :     prev_shmem_startup_hook = shmem_startup_hook;
     607           6 :     shmem_startup_hook = injection_shmem_startup;
     608             : }

Generated by: LCOV version 1.16