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

Generated by: LCOV version 1.14