LCOV - code coverage report
Current view: top level - src/backend/storage/lmgr - condition_variable.c (source / functions) Hit Total Coverage
Test: PostgreSQL 15devel Lines: 89 93 95.7 %
Date: 2021-12-05 01:09:12 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-2021, 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    31872150 : ConditionVariableInit(ConditionVariable *cv)
      38             : {
      39    31872150 :     SpinLockInit(&cv->mutex);
      40    31872150 :     proclist_init(&cv->wakeup);
      41    31872150 : }
      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        4456 : ConditionVariablePrepareToSleep(ConditionVariable *cv)
      59             : {
      60        4456 :     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        4456 :     if (cv_sleep_target != NULL)
      71           0 :         ConditionVariableCancelSleep();
      72             : 
      73             :     /* Record the condition variable on which we will sleep. */
      74        4456 :     cv_sleep_target = cv;
      75             : 
      76             :     /* Add myself to the wait queue. */
      77        4456 :     SpinLockAcquire(&cv->mutex);
      78        4456 :     proclist_push_tail(&cv->wakeup, pgprocno, cvWaitLink);
      79        4456 :     SpinLockRelease(&cv->mutex);
      80        4456 : }
      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        1844 : ConditionVariableSleep(ConditionVariable *cv, uint32 wait_event_info)
      99             : {
     100        1844 :     (void) ConditionVariableTimedSleep(cv, -1 /* no timeout */ ,
     101             :                                        wait_event_info);
     102        1844 : }
     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        1852 : ConditionVariableTimedSleep(ConditionVariable *cv, long timeout,
     113             :                             uint32 wait_event_info)
     114             : {
     115        1852 :     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        1852 :     if (cv_sleep_target != cv)
     136             :     {
     137          78 :         ConditionVariablePrepareToSleep(cv);
     138          78 :         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        1774 :     if (timeout >= 0)
     146             :     {
     147           4 :         INSTR_TIME_SET_CURRENT(start_time);
     148             :         Assert(timeout >= 0 && timeout <= INT_MAX);
     149           4 :         cur_timeout = timeout;
     150           4 :         wait_events = WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH;
     151             :     }
     152             :     else
     153        1770 :         wait_events = WL_LATCH_SET | WL_EXIT_ON_PM_DEATH;
     154             : 
     155             :     while (true)
     156         762 :     {
     157        2536 :         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        2536 :         (void) WaitLatch(MyLatch, wait_events, cur_timeout, wait_event_info);
     164             : 
     165             :         /* Reset latch before examining the state of the wait list. */
     166        2536 :         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        2536 :         SpinLockAcquire(&cv->mutex);
     184        2536 :         if (!proclist_contains(&cv->wakeup, MyProc->pgprocno, cvWaitLink))
     185             :         {
     186        1772 :             done = true;
     187        1772 :             proclist_push_tail(&cv->wakeup, MyProc->pgprocno, cvWaitLink);
     188             :         }
     189        2536 :         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        2536 :         CHECK_FOR_INTERRUPTS();
     197        2536 :         if (cv != cv_sleep_target)
     198           0 :             done = true;
     199             : 
     200             :         /* We were signaled, so return */
     201        2536 :         if (done)
     202        1772 :             return false;
     203             : 
     204             :         /* If we're not done, update cur_timeout for next iteration */
     205         764 :         if (timeout >= 0)
     206             :         {
     207           4 :             INSTR_TIME_SET_CURRENT(cur_time);
     208           4 :             INSTR_TIME_SUBTRACT(cur_time, start_time);
     209           4 :             cur_timeout = timeout - (long) INSTR_TIME_GET_MILLISEC(cur_time);
     210             : 
     211             :             /* Have we crossed the timeout threshold? */
     212           4 :             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             : void
     228       57234 : ConditionVariableCancelSleep(void)
     229             : {
     230       57234 :     ConditionVariable *cv = cv_sleep_target;
     231       57234 :     bool        signaled = false;
     232             : 
     233       57234 :     if (cv == NULL)
     234       52778 :         return;
     235             : 
     236        4456 :     SpinLockAcquire(&cv->mutex);
     237        4456 :     if (proclist_contains(&cv->wakeup, MyProc->pgprocno, cvWaitLink))
     238        4456 :         proclist_delete(&cv->wakeup, MyProc->pgprocno, cvWaitLink);
     239             :     else
     240           0 :         signaled = true;
     241        4456 :     SpinLockRelease(&cv->mutex);
     242             : 
     243             :     /*
     244             :      * If we've received a signal, pass it on to another waiting process, if
     245             :      * there is one.  Otherwise a call to ConditionVariableSignal() might get
     246             :      * lost, despite there being another process ready to handle it.
     247             :      */
     248        4456 :     if (signaled)
     249           0 :         ConditionVariableSignal(cv);
     250             : 
     251        4456 :     cv_sleep_target = NULL;
     252             : }
     253             : 
     254             : /*
     255             :  * Wake up the oldest process sleeping on the CV, if there is any.
     256             :  *
     257             :  * Note: it's difficult to tell whether this has any real effect: we know
     258             :  * whether we took an entry off the list, but the entry might only be a
     259             :  * sentinel.  Hence, think twice before proposing that this should return
     260             :  * a flag telling whether it woke somebody.
     261             :  */
     262             : void
     263        1048 : ConditionVariableSignal(ConditionVariable *cv)
     264             : {
     265        1048 :     PGPROC     *proc = NULL;
     266             : 
     267             :     /* Remove the first process from the wakeup queue (if any). */
     268        1048 :     SpinLockAcquire(&cv->mutex);
     269        1048 :     if (!proclist_is_empty(&cv->wakeup))
     270          74 :         proc = proclist_pop_head_node(&cv->wakeup, cvWaitLink);
     271        1048 :     SpinLockRelease(&cv->mutex);
     272             : 
     273             :     /* If we found someone sleeping, set their latch to wake them up. */
     274        1048 :     if (proc != NULL)
     275          74 :         SetLatch(&proc->procLatch);
     276        1048 : }
     277             : 
     278             : /*
     279             :  * Wake up all processes sleeping on the given CV.
     280             :  *
     281             :  * This guarantees to wake all processes that were sleeping on the CV
     282             :  * at time of call, but processes that add themselves to the list mid-call
     283             :  * will typically not get awakened.
     284             :  */
     285             : void
     286     2329566 : ConditionVariableBroadcast(ConditionVariable *cv)
     287             : {
     288     2329566 :     int         pgprocno = MyProc->pgprocno;
     289     2329566 :     PGPROC     *proc = NULL;
     290     2329566 :     bool        have_sentinel = false;
     291             : 
     292             :     /*
     293             :      * In some use-cases, it is common for awakened processes to immediately
     294             :      * re-queue themselves.  If we just naively try to reduce the wakeup list
     295             :      * to empty, we'll get into a potentially-indefinite loop against such a
     296             :      * process.  The semantics we really want are just to be sure that we have
     297             :      * wakened all processes that were in the list at entry.  We can use our
     298             :      * own cvWaitLink as a sentinel to detect when we've finished.
     299             :      *
     300             :      * A seeming flaw in this approach is that someone else might signal the
     301             :      * CV and in doing so remove our sentinel entry.  But that's fine: since
     302             :      * CV waiters are always added and removed in order, that must mean that
     303             :      * every previous waiter has been wakened, so we're done.  We'll get an
     304             :      * extra "set" on our latch from the someone else's signal, which is
     305             :      * slightly inefficient but harmless.
     306             :      *
     307             :      * We can't insert our cvWaitLink as a sentinel if it's already in use in
     308             :      * some other proclist.  While that's not expected to be true for typical
     309             :      * uses of this function, we can deal with it by simply canceling any
     310             :      * prepared CV sleep.  The next call to ConditionVariableSleep will take
     311             :      * care of re-establishing the lost state.
     312             :      */
     313     2329566 :     if (cv_sleep_target != NULL)
     314           6 :         ConditionVariableCancelSleep();
     315             : 
     316             :     /*
     317             :      * Inspect the state of the queue.  If it's empty, we have nothing to do.
     318             :      * If there's exactly one entry, we need only remove and signal that
     319             :      * entry.  Otherwise, remove the first entry and insert our sentinel.
     320             :      */
     321     2329566 :     SpinLockAcquire(&cv->mutex);
     322             :     /* While we're here, let's assert we're not in the list. */
     323             :     Assert(!proclist_contains(&cv->wakeup, pgprocno, cvWaitLink));
     324             : 
     325     2329566 :     if (!proclist_is_empty(&cv->wakeup))
     326             :     {
     327        1056 :         proc = proclist_pop_head_node(&cv->wakeup, cvWaitLink);
     328        1056 :         if (!proclist_is_empty(&cv->wakeup))
     329             :         {
     330           2 :             proclist_push_tail(&cv->wakeup, pgprocno, cvWaitLink);
     331           2 :             have_sentinel = true;
     332             :         }
     333             :     }
     334     2329566 :     SpinLockRelease(&cv->mutex);
     335             : 
     336             :     /* Awaken first waiter, if there was one. */
     337     2329566 :     if (proc != NULL)
     338        1056 :         SetLatch(&proc->procLatch);
     339             : 
     340     2329570 :     while (have_sentinel)
     341             :     {
     342             :         /*
     343             :          * Each time through the loop, remove the first wakeup list entry, and
     344             :          * signal it unless it's our sentinel.  Repeat as long as the sentinel
     345             :          * remains in the list.
     346             :          *
     347             :          * Notice that if someone else removes our sentinel, we will waken one
     348             :          * additional process before exiting.  That's intentional, because if
     349             :          * someone else signals the CV, they may be intending to waken some
     350             :          * third process that added itself to the list after we added the
     351             :          * sentinel.  Better to give a spurious wakeup (which should be
     352             :          * harmless beyond wasting some cycles) than to lose a wakeup.
     353             :          */
     354           4 :         proc = NULL;
     355           4 :         SpinLockAcquire(&cv->mutex);
     356           4 :         if (!proclist_is_empty(&cv->wakeup))
     357           4 :             proc = proclist_pop_head_node(&cv->wakeup, cvWaitLink);
     358           4 :         have_sentinel = proclist_contains(&cv->wakeup, pgprocno, cvWaitLink);
     359           4 :         SpinLockRelease(&cv->mutex);
     360             : 
     361           4 :         if (proc != NULL && proc != MyProc)
     362           2 :             SetLatch(&proc->procLatch);
     363             :     }
     364     2329566 : }

Generated by: LCOV version 1.14