|
| 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-2016, 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 "storage/condition_variable.h" |
| 23 | +#include "storage/ipc.h" |
| 24 | +#include "storage/proc.h" |
| 25 | +#include "storage/proclist.h" |
| 26 | +#include "storage/spin.h" |
| 27 | +#include "utils/memutils.h" |
| 28 | + |
| 29 | +/* Initially, we are not prepared to sleep on any condition variable. */ |
| 30 | +static ConditionVariable *cv_sleep_target = NULL; |
| 31 | + |
| 32 | +/* Reusable WaitEventSet. */ |
| 33 | +static WaitEventSet *cv_wait_event_set = NULL; |
| 34 | + |
| 35 | +/* |
| 36 | + * Initialize a condition variable. |
| 37 | + */ |
| 38 | +void |
| 39 | +ConditionVariableInit(ConditionVariable *cv) |
| 40 | +{ |
| 41 | + SpinLockInit(&cv->mutex); |
| 42 | + proclist_init(&cv->wakeup); |
| 43 | +} |
| 44 | + |
| 45 | +/* |
| 46 | + * Prepare to wait on a given condition variable. This can optionally be |
| 47 | + * called before entering a test/sleep loop. Alternatively, the call to |
| 48 | + * ConditionVariablePrepareToSleep can be omitted. The only advantage of |
| 49 | + * calling ConditionVariablePrepareToSleep is that it avoids an initial |
| 50 | + * double-test of the user's predicate in the case that we need to wait. |
| 51 | + */ |
| 52 | +void |
| 53 | +ConditionVariablePrepareToSleep(ConditionVariable *cv) |
| 54 | +{ |
| 55 | + int pgprocno = MyProc->pgprocno; |
| 56 | + |
| 57 | + /* |
| 58 | + * It's not legal to prepare a sleep until the previous sleep has been |
| 59 | + * completed or canceled. |
| 60 | + */ |
| 61 | + Assert(cv_sleep_target == NULL); |
| 62 | + |
| 63 | + /* Record the condition variable on which we will sleep. */ |
| 64 | + cv_sleep_target = cv; |
| 65 | + |
| 66 | + /* Create a reusable WaitEventSet. */ |
| 67 | + if (cv_wait_event_set == NULL) |
| 68 | + { |
| 69 | + cv_wait_event_set = CreateWaitEventSet(TopMemoryContext, 1); |
| 70 | + AddWaitEventToSet(cv_wait_event_set, WL_LATCH_SET, PGINVALID_SOCKET, |
| 71 | + &MyProc->procLatch, NULL); |
| 72 | + } |
| 73 | + |
| 74 | + /* Add myself to the wait queue. */ |
| 75 | + SpinLockAcquire(&cv->mutex); |
| 76 | + if (!proclist_contains(&cv->wakeup, pgprocno, cvWaitLink)) |
| 77 | + proclist_push_tail(&cv->wakeup, pgprocno, cvWaitLink); |
| 78 | + SpinLockRelease(&cv->mutex); |
| 79 | + |
| 80 | + /* Reset my latch before entering the caller's predicate loop. */ |
| 81 | + ResetLatch(&MyProc->procLatch); |
| 82 | +} |
| 83 | + |
| 84 | +/*-------------------------------------------------------------------------- |
| 85 | + * Wait for the given condition variable to be signaled. This should be |
| 86 | + * called in a predicate loop that tests for a specfic exit condition and |
| 87 | + * otherwise sleeps, like so: |
| 88 | + * |
| 89 | + * ConditionVariablePrepareToSleep(cv); [optional] |
| 90 | + * while (condition for which we are waiting is not true) |
| 91 | + * ConditionVariableSleep(cv, wait_event_info); |
| 92 | + * ConditionVariableCancelSleep(); |
| 93 | + * |
| 94 | + * Supply a value from one of the WaitEventXXX enums defined in pgstat.h to |
| 95 | + * control the contents of pg_stat_activity's wait_event_type and wait_event |
| 96 | + * columns while waiting. |
| 97 | + *-------------------------------------------------------------------------*/ |
| 98 | +void |
| 99 | +ConditionVariableSleep(ConditionVariable *cv, uint32 wait_event_info) |
| 100 | +{ |
| 101 | + WaitEvent event; |
| 102 | + bool done = false; |
| 103 | + |
| 104 | + /* |
| 105 | + * If the caller didn't prepare to sleep explicitly, then do so now and |
| 106 | + * return immediately. The caller's predicate loop should immediately |
| 107 | + * call again if its exit condition is not yet met. This initial spurious |
| 108 | + * return can be avoided by calling ConditionVariablePrepareToSleep(cv) |
| 109 | + * first. Whether it's worth doing that depends on whether you expect the |
| 110 | + * condition to be met initially, in which case skipping the prepare |
| 111 | + * allows you to skip manipulation of the wait list, or not met intiailly, |
| 112 | + * in which case preparing first allows you to skip a spurious test of the |
| 113 | + * caller's exit condition. |
| 114 | + */ |
| 115 | + if (cv_sleep_target == NULL) |
| 116 | + { |
| 117 | + ConditionVariablePrepareToSleep(cv); |
| 118 | + return; |
| 119 | + } |
| 120 | + |
| 121 | + /* Any earlier condition variable sleep must have been canceled. */ |
| 122 | + Assert(cv_sleep_target == cv); |
| 123 | + |
| 124 | + while (!done) |
| 125 | + { |
| 126 | + CHECK_FOR_INTERRUPTS(); |
| 127 | + |
| 128 | + /* |
| 129 | + * Wait for latch to be set. We don't care about the result because |
| 130 | + * our contract permits spurious returns. |
| 131 | + */ |
| 132 | + WaitEventSetWait(cv_wait_event_set, -1, &event, 1, wait_event_info); |
| 133 | + |
| 134 | + /* Reset latch before testing whether we can return. */ |
| 135 | + ResetLatch(&MyProc->procLatch); |
| 136 | + |
| 137 | + /* |
| 138 | + * If this process has been taken out of the wait list, then we know |
| 139 | + * that is has been signaled by ConditionVariableSignal. We put it |
| 140 | + * back into the wait list, so we don't miss any further signals while |
| 141 | + * the caller's loop checks its condition. If it hasn't been taken |
| 142 | + * out of the wait list, then the latch must have been set by |
| 143 | + * something other than ConditionVariableSignal; though we don't |
| 144 | + * guarantee not to return spuriously, we'll avoid these obvious |
| 145 | + * cases. |
| 146 | + */ |
| 147 | + SpinLockAcquire(&cv->mutex); |
| 148 | + if (!proclist_contains(&cv->wakeup, MyProc->pgprocno, cvWaitLink)) |
| 149 | + { |
| 150 | + done = true; |
| 151 | + proclist_push_tail(&cv->wakeup, MyProc->pgprocno, cvWaitLink); |
| 152 | + } |
| 153 | + SpinLockRelease(&cv->mutex); |
| 154 | + } |
| 155 | +} |
| 156 | + |
| 157 | +/* |
| 158 | + * Cancel any pending sleep operation. We just need to remove ourselves |
| 159 | + * from the wait queue of any condition variable for which we have previously |
| 160 | + * prepared a sleep. |
| 161 | + */ |
| 162 | +void |
| 163 | +ConditionVariableCancelSleep(void) |
| 164 | +{ |
| 165 | + ConditionVariable *cv = cv_sleep_target; |
| 166 | + |
| 167 | + if (cv == NULL) |
| 168 | + return; |
| 169 | + |
| 170 | + SpinLockAcquire(&cv->mutex); |
| 171 | + if (proclist_contains(&cv->wakeup, MyProc->pgprocno, cvWaitLink)) |
| 172 | + proclist_delete(&cv->wakeup, MyProc->pgprocno, cvWaitLink); |
| 173 | + SpinLockRelease(&cv->mutex); |
| 174 | + |
| 175 | + cv_sleep_target = NULL; |
| 176 | +} |
| 177 | + |
| 178 | +/* |
| 179 | + * Wake up one sleeping process, assuming there is at least one. |
| 180 | + * |
| 181 | + * The return value indicates whether or not we woke somebody up. |
| 182 | + */ |
| 183 | +bool |
| 184 | +ConditionVariableSignal(ConditionVariable *cv) |
| 185 | +{ |
| 186 | + PGPROC *proc = NULL; |
| 187 | + |
| 188 | + /* Remove the first process from the wakeup queue (if any). */ |
| 189 | + SpinLockAcquire(&cv->mutex); |
| 190 | + if (!proclist_is_empty(&cv->wakeup)) |
| 191 | + proc = proclist_pop_head_node(&cv->wakeup, cvWaitLink); |
| 192 | + SpinLockRelease(&cv->mutex); |
| 193 | + |
| 194 | + /* If we found someone sleeping, set their latch to wake them up. */ |
| 195 | + if (proc != NULL) |
| 196 | + { |
| 197 | + SetLatch(&proc->procLatch); |
| 198 | + return true; |
| 199 | + } |
| 200 | + |
| 201 | + /* No sleeping processes. */ |
| 202 | + return false; |
| 203 | +} |
| 204 | + |
| 205 | +/* |
| 206 | + * Wake up all sleeping processes. |
| 207 | + * |
| 208 | + * The return value indicates the number of processes we woke. |
| 209 | + */ |
| 210 | +int |
| 211 | +ConditionVariableBroadcast(ConditionVariable *cv) |
| 212 | +{ |
| 213 | + int nwoken = 0; |
| 214 | + |
| 215 | + /* |
| 216 | + * Let's just do this the dumbest way possible. We could try to dequeue |
| 217 | + * all the sleepers at once to save spinlock cycles, but it's a bit hard |
| 218 | + * to get that right in the face of possible sleep cancelations, and |
| 219 | + * we don't want to loop holding the mutex. |
| 220 | + */ |
| 221 | + while (ConditionVariableSignal(cv)) |
| 222 | + ++nwoken; |
| 223 | + |
| 224 | + return nwoken; |
| 225 | +} |
0 commit comments