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: 2023-12-01 18:10:44 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-2023, 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/ipc.h"
      25             : #include "storage/proc.h"
      26             : #include "storage/proclist.h"
      27             : #include "storage/spin.h"
      28             : #include "utils/memutils.h"
      29             : 
      30             : /* Initially, we are not prepared to sleep on any condition variable. */
      31             : static ConditionVariable *cv_sleep_target = NULL;
      32             : 
      33             : /*
      34             :  * Initialize a condition variable.
      35             :  */
      36             : void
      37    14773724 : ConditionVariableInit(ConditionVariable *cv)
      38             : {
      39    14773724 :     SpinLockInit(&cv->mutex);
      40    14773724 :     proclist_init(&cv->wakeup);
      41    14773724 : }
      42             : 
      43             : /*
      44             :  * Prepare to wait on a given condition variable.
      45             :  *
      46             :  * This can optionally be called before entering a test/sleep loop.
      47             :  * Doing so is more efficient if we'll need to sleep at least once.
      48             :  * However, if the first test of the exit condition is likely to succeed,
      49             :  * it's more efficient to omit the ConditionVariablePrepareToSleep call.
      50             :  * See comments in ConditionVariableSleep for more detail.
      51             :  *
      52             :  * Caution: "before entering the loop" means you *must* test the exit
      53             :  * condition between calling ConditionVariablePrepareToSleep and calling
      54             :  * ConditionVariableSleep.  If that is inconvenient, omit calling
      55             :  * ConditionVariablePrepareToSleep.
      56             :  */
      57             : void
      58      129406 : ConditionVariablePrepareToSleep(ConditionVariable *cv)
      59             : {
      60      129406 :     int         pgprocno = MyProc->pgprocno;
      61             : 
      62             :     /*
      63             :      * If some other sleep is already prepared, cancel it; this is necessary
      64             :      * because we have just one static variable tracking the prepared sleep,
      65             :      * and also only one cvWaitLink in our PGPROC.  It's okay to do this
      66             :      * because whenever control does return to the other test-and-sleep loop,
      67             :      * its ConditionVariableSleep call will just re-establish that sleep as
      68             :      * the prepared one.
      69             :      */
      70      129406 :     if (cv_sleep_target != NULL)
      71           0 :         ConditionVariableCancelSleep();
      72             : 
      73             :     /* Record the condition variable on which we will sleep. */
      74      129406 :     cv_sleep_target = cv;
      75             : 
      76             :     /* Add myself to the wait queue. */
      77      129406 :     SpinLockAcquire(&cv->mutex);
      78      129406 :     proclist_push_tail(&cv->wakeup, pgprocno, cvWaitLink);
      79      129406 :     SpinLockRelease(&cv->mutex);
      80      129406 : }
      81             : 
      82             : /*
      83             :  * Wait for the given condition variable to be signaled.
      84             :  *
      85             :  * This should be called in a predicate loop that tests for a specific exit
      86             :  * condition and otherwise sleeps, like so:
      87             :  *
      88             :  *   ConditionVariablePrepareToSleep(cv);  // optional
      89             :  *   while (condition for which we are waiting is not true)
      90             :  *       ConditionVariableSleep(cv, wait_event_info);
      91             :  *   ConditionVariableCancelSleep();
      92             :  *
      93             :  * wait_event_info should be a value from one of the WaitEventXXX enums
      94             :  * defined in pgstat.h.  This controls the contents of pg_stat_activity's
      95             :  * wait_event_type and wait_event columns while waiting.
      96             :  */
      97             : void
      98        1990 : ConditionVariableSleep(ConditionVariable *cv, uint32 wait_event_info)
      99             : {
     100        1990 :     (void) ConditionVariableTimedSleep(cv, -1 /* no timeout */ ,
     101             :                                        wait_event_info);
     102        1990 : }
     103             : 
     104             : /*
     105             :  * Wait for a condition variable to be signaled or a timeout to be reached.
     106             :  *
     107             :  * Returns true when timeout expires, otherwise returns false.
     108             :  *
     109             :  * See ConditionVariableSleep() for general usage.
     110             :  */
     111             : bool
     112        2380 : ConditionVariableTimedSleep(ConditionVariable *cv, long timeout,
     113             :                             uint32 wait_event_info)
     114             : {
     115        2380 :     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        2380 :     if (cv_sleep_target != cv)
     136             :     {
     137         310 :         ConditionVariablePrepareToSleep(cv);
     138         310 :         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        2070 :     if (timeout >= 0)
     146             :     {
     147         194 :         INSTR_TIME_SET_CURRENT(start_time);
     148             :         Assert(timeout >= 0 && timeout <= INT_MAX);
     149         194 :         cur_timeout = timeout;
     150         194 :         wait_events = WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH;
     151             :     }
     152             :     else
     153        1876 :         wait_events = WL_LATCH_SET | WL_EXIT_ON_PM_DEATH;
     154             : 
     155             :     while (true)
     156         848 :     {
     157        2918 :         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        2918 :         (void) WaitLatch(MyLatch, wait_events, cur_timeout, wait_event_info);
     164             : 
     165             :         /* Reset latch before examining the state of the wait list. */
     166        2918 :         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        2918 :         SpinLockAcquire(&cv->mutex);
     184        2918 :         if (!proclist_contains(&cv->wakeup, MyProc->pgprocno, cvWaitLink))
     185             :         {
     186        1976 :             done = true;
     187        1976 :             proclist_push_tail(&cv->wakeup, MyProc->pgprocno, cvWaitLink);
     188             :         }
     189        2918 :         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        2918 :         CHECK_FOR_INTERRUPTS();
     197        2918 :         if (cv != cv_sleep_target)
     198          94 :             done = true;
     199             : 
     200             :         /* We were signaled, so return */
     201        2918 :         if (done)
     202        2068 :             return false;
     203             : 
     204             :         /* If we're not done, update cur_timeout for next iteration */
     205         850 :         if (timeout >= 0)
     206             :         {
     207          40 :             INSTR_TIME_SET_CURRENT(cur_time);
     208          40 :             INSTR_TIME_SUBTRACT(cur_time, start_time);
     209          40 :             cur_timeout = timeout - (long) INSTR_TIME_GET_MILLISEC(cur_time);
     210             : 
     211             :             /* Have we crossed the timeout threshold? */
     212          40 :             if (cur_timeout <= 0)
     213           2 :                 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      222534 : ConditionVariableCancelSleep(void)
     231             : {
     232      222534 :     ConditionVariable *cv = cv_sleep_target;
     233      222534 :     bool        signaled = false;
     234             : 
     235      222534 :     if (cv == NULL)
     236       93128 :         return false;
     237             : 
     238      129406 :     SpinLockAcquire(&cv->mutex);
     239      129406 :     if (proclist_contains(&cv->wakeup, MyProc->pgprocno, cvWaitLink))
     240       91788 :         proclist_delete(&cv->wakeup, MyProc->pgprocno, cvWaitLink);
     241             :     else
     242       37618 :         signaled = true;
     243      129406 :     SpinLockRelease(&cv->mutex);
     244             : 
     245      129406 :     cv_sleep_target = NULL;
     246             : 
     247      129406 :     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        1572 : ConditionVariableSignal(ConditionVariable *cv)
     260             : {
     261        1572 :     PGPROC     *proc = NULL;
     262             : 
     263             :     /* Remove the first process from the wakeup queue (if any). */
     264        1572 :     SpinLockAcquire(&cv->mutex);
     265        1572 :     if (!proclist_is_empty(&cv->wakeup))
     266         114 :         proc = proclist_pop_head_node(&cv->wakeup, cvWaitLink);
     267        1572 :     SpinLockRelease(&cv->mutex);
     268             : 
     269             :     /* If we found someone sleeping, set their latch to wake them up. */
     270        1572 :     if (proc != NULL)
     271         114 :         SetLatch(&proc->procLatch);
     272        1572 : }
     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     8766890 : ConditionVariableBroadcast(ConditionVariable *cv)
     283             : {
     284     8766890 :     int         pgprocno = MyProc->pgprocno;
     285     8766890 :     PGPROC     *proc = NULL;
     286     8766890 :     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     8766890 :     if (cv_sleep_target != NULL)
     310         110 :         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     8766890 :     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     8766890 :     if (!proclist_is_empty(&cv->wakeup))
     322             :     {
     323       39328 :         proc = proclist_pop_head_node(&cv->wakeup, cvWaitLink);
     324       39328 :         if (!proclist_is_empty(&cv->wakeup))
     325             :         {
     326         252 :             proclist_push_tail(&cv->wakeup, pgprocno, cvWaitLink);
     327         252 :             have_sentinel = true;
     328             :         }
     329             :     }
     330     8766890 :     SpinLockRelease(&cv->mutex);
     331             : 
     332             :     /* Awaken first waiter, if there was one. */
     333     8766890 :     if (proc != NULL)
     334       39328 :         SetLatch(&proc->procLatch);
     335             : 
     336     8767430 :     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         540 :         proc = NULL;
     351         540 :         SpinLockAcquire(&cv->mutex);
     352         540 :         if (!proclist_is_empty(&cv->wakeup))
     353         540 :             proc = proclist_pop_head_node(&cv->wakeup, cvWaitLink);
     354         540 :         have_sentinel = proclist_contains(&cv->wakeup, pgprocno, cvWaitLink);
     355         540 :         SpinLockRelease(&cv->mutex);
     356             : 
     357         540 :         if (proc != NULL && proc != MyProc)
     358         288 :             SetLatch(&proc->procLatch);
     359             :     }
     360     8766890 : }

Generated by: LCOV version 1.14