LCOV - code coverage report
Current view: top level - src/backend/storage/lmgr - condition_variable.c (source / functions) Hit Total Coverage
Test: PostgreSQL 17devel Lines: 91 92 98.9 %
Date: 2024-04-19 16:11:40 Functions: 7 7 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * condition_variable.c
       4             :  *    Implementation of condition variables.  Condition variables provide
       5             :  *    a way for one process to wait until a specific condition occurs,
       6             :  *    without needing to know the specific identity of the process for
       7             :  *    which they are waiting.  Waits for condition variables can be
       8             :  *    interrupted, unlike LWLock waits.  Condition variables are safe
       9             :  *    to use within dynamic shared memory segments.
      10             :  *
      11             :  * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
      12             :  * Portions Copyright (c) 1994, Regents of the University of California
      13             :  *
      14             :  * src/backend/storage/lmgr/condition_variable.c
      15             :  *
      16             :  *-------------------------------------------------------------------------
      17             :  */
      18             : 
      19             : #include "postgres.h"
      20             : 
      21             : #include "miscadmin.h"
      22             : #include "portability/instr_time.h"
      23             : #include "storage/condition_variable.h"
      24             : #include "storage/proc.h"
      25             : #include "storage/proclist.h"
      26             : #include "storage/spin.h"
      27             : 
      28             : /* Initially, we are not prepared to sleep on any condition variable. */
      29             : static ConditionVariable *cv_sleep_target = NULL;
      30             : 
      31             : /*
      32             :  * Initialize a condition variable.
      33             :  */
      34             : void
      35    16369964 : ConditionVariableInit(ConditionVariable *cv)
      36             : {
      37    16369964 :     SpinLockInit(&cv->mutex);
      38    16369964 :     proclist_init(&cv->wakeup);
      39    16369964 : }
      40             : 
      41             : /*
      42             :  * Prepare to wait on a given condition variable.
      43             :  *
      44             :  * This can optionally be called before entering a test/sleep loop.
      45             :  * Doing so is more efficient if we'll need to sleep at least once.
      46             :  * However, if the first test of the exit condition is likely to succeed,
      47             :  * it's more efficient to omit the ConditionVariablePrepareToSleep call.
      48             :  * See comments in ConditionVariableSleep for more detail.
      49             :  *
      50             :  * Caution: "before entering the loop" means you *must* test the exit
      51             :  * condition between calling ConditionVariablePrepareToSleep and calling
      52             :  * ConditionVariableSleep.  If that is inconvenient, omit calling
      53             :  * ConditionVariablePrepareToSleep.
      54             :  */
      55             : void
      56      142890 : ConditionVariablePrepareToSleep(ConditionVariable *cv)
      57             : {
      58      142890 :     int         pgprocno = MyProcNumber;
      59             : 
      60             :     /*
      61             :      * If some other sleep is already prepared, cancel it; this is necessary
      62             :      * because we have just one static variable tracking the prepared sleep,
      63             :      * and also only one cvWaitLink in our PGPROC.  It's okay to do this
      64             :      * because whenever control does return to the other test-and-sleep loop,
      65             :      * its ConditionVariableSleep call will just re-establish that sleep as
      66             :      * the prepared one.
      67             :      */
      68      142890 :     if (cv_sleep_target != NULL)
      69           0 :         ConditionVariableCancelSleep();
      70             : 
      71             :     /* Record the condition variable on which we will sleep. */
      72      142890 :     cv_sleep_target = cv;
      73             : 
      74             :     /* Add myself to the wait queue. */
      75      142890 :     SpinLockAcquire(&cv->mutex);
      76      142890 :     proclist_push_tail(&cv->wakeup, pgprocno, cvWaitLink);
      77      142890 :     SpinLockRelease(&cv->mutex);
      78      142890 : }
      79             : 
      80             : /*
      81             :  * Wait for the given condition variable to be signaled.
      82             :  *
      83             :  * This should be called in a predicate loop that tests for a specific exit
      84             :  * condition and otherwise sleeps, like so:
      85             :  *
      86             :  *   ConditionVariablePrepareToSleep(cv);  // optional
      87             :  *   while (condition for which we are waiting is not true)
      88             :  *       ConditionVariableSleep(cv, wait_event_info);
      89             :  *   ConditionVariableCancelSleep();
      90             :  *
      91             :  * wait_event_info should be a value from one of the WaitEventXXX enums
      92             :  * defined in pgstat.h.  This controls the contents of pg_stat_activity's
      93             :  * wait_event_type and wait_event columns while waiting.
      94             :  */
      95             : void
      96        2458 : ConditionVariableSleep(ConditionVariable *cv, uint32 wait_event_info)
      97             : {
      98        2458 :     (void) ConditionVariableTimedSleep(cv, -1 /* no timeout */ ,
      99             :                                        wait_event_info);
     100        2458 : }
     101             : 
     102             : /*
     103             :  * Wait for a condition variable to be signaled or a timeout to be reached.
     104             :  *
     105             :  * The "timeout" is given in milliseconds.
     106             :  *
     107             :  * Returns true when timeout expires, otherwise returns false.
     108             :  *
     109             :  * See ConditionVariableSleep() for general usage.
     110             :  */
     111             : bool
     112        2896 : ConditionVariableTimedSleep(ConditionVariable *cv, long timeout,
     113             :                             uint32 wait_event_info)
     114             : {
     115        2896 :     long        cur_timeout = -1;
     116             :     instr_time  start_time;
     117             :     instr_time  cur_time;
     118             :     int         wait_events;
     119             : 
     120             :     /*
     121             :      * If the caller didn't prepare to sleep explicitly, then do so now and
     122             :      * return immediately.  The caller's predicate loop should immediately
     123             :      * call again if its exit condition is not yet met.  This will result in
     124             :      * the exit condition being tested twice before we first sleep.  The extra
     125             :      * test can be prevented by calling ConditionVariablePrepareToSleep(cv)
     126             :      * first.  Whether it's worth doing that depends on whether you expect the
     127             :      * exit condition to be met initially, in which case skipping the prepare
     128             :      * is recommended because it avoids manipulations of the wait list, or not
     129             :      * met initially, in which case preparing first is better because it
     130             :      * avoids one extra test of the exit condition.
     131             :      *
     132             :      * If we are currently prepared to sleep on some other CV, we just cancel
     133             :      * that and prepare this one; see ConditionVariablePrepareToSleep.
     134             :      */
     135        2896 :     if (cv_sleep_target != cv)
     136             :     {
     137         320 :         ConditionVariablePrepareToSleep(cv);
     138         320 :         return false;
     139             :     }
     140             : 
     141             :     /*
     142             :      * Record the current time so that we can calculate the remaining timeout
     143             :      * if we are woken up spuriously.
     144             :      */
     145        2576 :     if (timeout >= 0)
     146             :     {
     147         220 :         INSTR_TIME_SET_CURRENT(start_time);
     148             :         Assert(timeout >= 0 && timeout <= INT_MAX);
     149         220 :         cur_timeout = timeout;
     150         220 :         wait_events = WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH;
     151             :     }
     152             :     else
     153        2356 :         wait_events = WL_LATCH_SET | WL_EXIT_ON_PM_DEATH;
     154             : 
     155             :     while (true)
     156         714 :     {
     157        3290 :         bool        done = false;
     158             : 
     159             :         /*
     160             :          * Wait for latch to be set.  (If we're awakened for some other
     161             :          * reason, the code below will cope anyway.)
     162             :          */
     163        3290 :         (void) WaitLatch(MyLatch, wait_events, cur_timeout, wait_event_info);
     164             : 
     165             :         /* Reset latch before examining the state of the wait list. */
     166        3290 :         ResetLatch(MyLatch);
     167             : 
     168             :         /*
     169             :          * If this process has been taken out of the wait list, then we know
     170             :          * that it has been signaled by ConditionVariableSignal (or
     171             :          * ConditionVariableBroadcast), so we should return to the caller. But
     172             :          * that doesn't guarantee that the exit condition is met, only that we
     173             :          * ought to check it.  So we must put the process back into the wait
     174             :          * list, to ensure we don't miss any additional wakeup occurring while
     175             :          * the caller checks its exit condition.  We can take ourselves out of
     176             :          * the wait list only when the caller calls
     177             :          * ConditionVariableCancelSleep.
     178             :          *
     179             :          * If we're still in the wait list, then the latch must have been set
     180             :          * by something other than ConditionVariableSignal; though we don't
     181             :          * guarantee not to return spuriously, we'll avoid this obvious case.
     182             :          */
     183        3290 :         SpinLockAcquire(&cv->mutex);
     184        3290 :         if (!proclist_contains(&cv->wakeup, MyProcNumber, cvWaitLink))
     185             :         {
     186        2466 :             done = true;
     187        2466 :             proclist_push_tail(&cv->wakeup, MyProcNumber, cvWaitLink);
     188             :         }
     189        3290 :         SpinLockRelease(&cv->mutex);
     190             : 
     191             :         /*
     192             :          * Check for interrupts, and return spuriously if that caused the
     193             :          * current sleep target to change (meaning that interrupt handler code
     194             :          * waited for a different condition variable).
     195             :          */
     196        3290 :         CHECK_FOR_INTERRUPTS();
     197        3290 :         if (cv != cv_sleep_target)
     198         108 :             done = true;
     199             : 
     200             :         /* We were signaled, so return */
     201        3290 :         if (done)
     202        2572 :             return false;
     203             : 
     204             :         /* If we're not done, update cur_timeout for next iteration */
     205         718 :         if (timeout >= 0)
     206             :         {
     207          46 :             INSTR_TIME_SET_CURRENT(cur_time);
     208          46 :             INSTR_TIME_SUBTRACT(cur_time, start_time);
     209          46 :             cur_timeout = timeout - (long) INSTR_TIME_GET_MILLISEC(cur_time);
     210             : 
     211             :             /* Have we crossed the timeout threshold? */
     212          46 :             if (cur_timeout <= 0)
     213           4 :                 return true;
     214             :         }
     215             :     }
     216             : }
     217             : 
     218             : /*
     219             :  * Cancel any pending sleep operation.
     220             :  *
     221             :  * We just need to remove ourselves from the wait queue of any condition
     222             :  * variable for which we have previously prepared a sleep.
     223             :  *
     224             :  * Do nothing if nothing is pending; this allows this function to be called
     225             :  * during transaction abort to clean up any unfinished CV sleep.
     226             :  *
     227             :  * Return true if we've been signaled.
     228             :  */
     229             : bool
     230      243272 : ConditionVariableCancelSleep(void)
     231             : {
     232      243272 :     ConditionVariable *cv = cv_sleep_target;
     233      243272 :     bool        signaled = false;
     234             : 
     235      243272 :     if (cv == NULL)
     236      100382 :         return false;
     237             : 
     238      142890 :     SpinLockAcquire(&cv->mutex);
     239      142890 :     if (proclist_contains(&cv->wakeup, MyProcNumber, cvWaitLink))
     240      101924 :         proclist_delete(&cv->wakeup, MyProcNumber, cvWaitLink);
     241             :     else
     242       40966 :         signaled = true;
     243      142890 :     SpinLockRelease(&cv->mutex);
     244             : 
     245      142890 :     cv_sleep_target = NULL;
     246             : 
     247      142890 :     return signaled;
     248             : }
     249             : 
     250             : /*
     251             :  * Wake up the oldest process sleeping on the CV, if there is any.
     252             :  *
     253             :  * Note: it's difficult to tell whether this has any real effect: we know
     254             :  * whether we took an entry off the list, but the entry might only be a
     255             :  * sentinel.  Hence, think twice before proposing that this should return
     256             :  * a flag telling whether it woke somebody.
     257             :  */
     258             : void
     259        1622 : ConditionVariableSignal(ConditionVariable *cv)
     260             : {
     261        1622 :     PGPROC     *proc = NULL;
     262             : 
     263             :     /* Remove the first process from the wakeup queue (if any). */
     264        1622 :     SpinLockAcquire(&cv->mutex);
     265        1622 :     if (!proclist_is_empty(&cv->wakeup))
     266         102 :         proc = proclist_pop_head_node(&cv->wakeup, cvWaitLink);
     267        1622 :     SpinLockRelease(&cv->mutex);
     268             : 
     269             :     /* If we found someone sleeping, set their latch to wake them up. */
     270        1622 :     if (proc != NULL)
     271         102 :         SetLatch(&proc->procLatch);
     272        1622 : }
     273             : 
     274             : /*
     275             :  * Wake up all processes sleeping on the given CV.
     276             :  *
     277             :  * This guarantees to wake all processes that were sleeping on the CV
     278             :  * at time of call, but processes that add themselves to the list mid-call
     279             :  * will typically not get awakened.
     280             :  */
     281             : void
     282     8835348 : ConditionVariableBroadcast(ConditionVariable *cv)
     283             : {
     284     8835348 :     int         pgprocno = MyProcNumber;
     285     8835348 :     PGPROC     *proc = NULL;
     286     8835348 :     bool        have_sentinel = false;
     287             : 
     288             :     /*
     289             :      * In some use-cases, it is common for awakened processes to immediately
     290             :      * re-queue themselves.  If we just naively try to reduce the wakeup list
     291             :      * to empty, we'll get into a potentially-indefinite loop against such a
     292             :      * process.  The semantics we really want are just to be sure that we have
     293             :      * wakened all processes that were in the list at entry.  We can use our
     294             :      * own cvWaitLink as a sentinel to detect when we've finished.
     295             :      *
     296             :      * A seeming flaw in this approach is that someone else might signal the
     297             :      * CV and in doing so remove our sentinel entry.  But that's fine: since
     298             :      * CV waiters are always added and removed in order, that must mean that
     299             :      * every previous waiter has been wakened, so we're done.  We'll get an
     300             :      * extra "set" on our latch from the someone else's signal, which is
     301             :      * slightly inefficient but harmless.
     302             :      *
     303             :      * We can't insert our cvWaitLink as a sentinel if it's already in use in
     304             :      * some other proclist.  While that's not expected to be true for typical
     305             :      * uses of this function, we can deal with it by simply canceling any
     306             :      * prepared CV sleep.  The next call to ConditionVariableSleep will take
     307             :      * care of re-establishing the lost state.
     308             :      */
     309     8835348 :     if (cv_sleep_target != NULL)
     310         126 :         ConditionVariableCancelSleep();
     311             : 
     312             :     /*
     313             :      * Inspect the state of the queue.  If it's empty, we have nothing to do.
     314             :      * If there's exactly one entry, we need only remove and signal that
     315             :      * entry.  Otherwise, remove the first entry and insert our sentinel.
     316             :      */
     317     8835348 :     SpinLockAcquire(&cv->mutex);
     318             :     /* While we're here, let's assert we're not in the list. */
     319             :     Assert(!proclist_contains(&cv->wakeup, pgprocno, cvWaitLink));
     320             : 
     321     8835348 :     if (!proclist_is_empty(&cv->wakeup))
     322             :     {
     323       43032 :         proc = proclist_pop_head_node(&cv->wakeup, cvWaitLink);
     324       43032 :         if (!proclist_is_empty(&cv->wakeup))
     325             :         {
     326         270 :             proclist_push_tail(&cv->wakeup, pgprocno, cvWaitLink);
     327         270 :             have_sentinel = true;
     328             :         }
     329             :     }
     330     8835348 :     SpinLockRelease(&cv->mutex);
     331             : 
     332             :     /* Awaken first waiter, if there was one. */
     333     8835348 :     if (proc != NULL)
     334       43032 :         SetLatch(&proc->procLatch);
     335             : 
     336     8835920 :     while (have_sentinel)
     337             :     {
     338             :         /*
     339             :          * Each time through the loop, remove the first wakeup list entry, and
     340             :          * signal it unless it's our sentinel.  Repeat as long as the sentinel
     341             :          * remains in the list.
     342             :          *
     343             :          * Notice that if someone else removes our sentinel, we will waken one
     344             :          * additional process before exiting.  That's intentional, because if
     345             :          * someone else signals the CV, they may be intending to waken some
     346             :          * third process that added itself to the list after we added the
     347             :          * sentinel.  Better to give a spurious wakeup (which should be
     348             :          * harmless beyond wasting some cycles) than to lose a wakeup.
     349             :          */
     350         572 :         proc = NULL;
     351         572 :         SpinLockAcquire(&cv->mutex);
     352         572 :         if (!proclist_is_empty(&cv->wakeup))
     353         572 :             proc = proclist_pop_head_node(&cv->wakeup, cvWaitLink);
     354         572 :         have_sentinel = proclist_contains(&cv->wakeup, pgprocno, cvWaitLink);
     355         572 :         SpinLockRelease(&cv->mutex);
     356             : 
     357         572 :         if (proc != NULL && proc != MyProc)
     358         302 :             SetLatch(&proc->procLatch);
     359             :     }
     360     8835348 : }

Generated by: LCOV version 1.14