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

Generated by: LCOV version 1.14