Commit e35dba47 authored by Tom Lane's avatar Tom Lane

Cosmetic improvements in condition_variable.[hc].

Clarify a bunch of comments.

Discussion: https://postgr.es/m/CAEepm=0NWKehYw7NDoUSf8juuKOPRnCyY3vuaSvhrEWsOTAa3w@mail.gmail.com
parent ea8e1bbc
...@@ -43,11 +43,22 @@ ConditionVariableInit(ConditionVariable *cv) ...@@ -43,11 +43,22 @@ ConditionVariableInit(ConditionVariable *cv)
} }
/* /*
* Prepare to wait on a given condition variable. This can optionally be * Prepare to wait on a given condition variable.
* called before entering a test/sleep loop. Alternatively, the call to *
* ConditionVariablePrepareToSleep can be omitted. The only advantage of * This can optionally be called before entering a test/sleep loop.
* calling ConditionVariablePrepareToSleep is that it avoids an initial * Doing so is more efficient if we'll need to sleep at least once.
* double-test of the user's predicate in the case that we need to wait. * However, if the first test of the exit condition is likely to succeed,
* it's more efficient to omit the ConditionVariablePrepareToSleep call.
* See comments in ConditionVariableSleep for more detail.
*
* Caution: "before entering the loop" means you *must* test the exit
* condition between calling ConditionVariablePrepareToSleep and calling
* ConditionVariableSleep. If that is inconvenient, omit calling
* ConditionVariablePrepareToSleep.
*
* Only one condition variable can be used at a time, ie,
* ConditionVariableCancelSleep must be called before any attempt is made
* to sleep on a different condition variable.
*/ */
void void
ConditionVariablePrepareToSleep(ConditionVariable *cv) ConditionVariablePrepareToSleep(ConditionVariable *cv)
...@@ -79,8 +90,8 @@ ConditionVariablePrepareToSleep(ConditionVariable *cv) ...@@ -79,8 +90,8 @@ ConditionVariablePrepareToSleep(ConditionVariable *cv)
cv_sleep_target = cv; cv_sleep_target = cv;
/* /*
* Reset my latch before adding myself to the queue and before entering * Reset my latch before adding myself to the queue, to ensure that we
* the caller's predicate loop. * don't miss a wakeup that occurs immediately.
*/ */
ResetLatch(MyLatch); ResetLatch(MyLatch);
...@@ -90,20 +101,21 @@ ConditionVariablePrepareToSleep(ConditionVariable *cv) ...@@ -90,20 +101,21 @@ ConditionVariablePrepareToSleep(ConditionVariable *cv)
SpinLockRelease(&cv->mutex); SpinLockRelease(&cv->mutex);
} }
/*-------------------------------------------------------------------------- /*
* Wait for the given condition variable to be signaled. This should be * Wait for the given condition variable to be signaled.
* called in a predicate loop that tests for a specific exit condition and *
* otherwise sleeps, like so: * This should be called in a predicate loop that tests for a specific exit
* condition and otherwise sleeps, like so:
* *
* ConditionVariablePrepareToSleep(cv); [optional] * ConditionVariablePrepareToSleep(cv); // optional
* while (condition for which we are waiting is not true) * while (condition for which we are waiting is not true)
* ConditionVariableSleep(cv, wait_event_info); * ConditionVariableSleep(cv, wait_event_info);
* ConditionVariableCancelSleep(); * ConditionVariableCancelSleep();
* *
* Supply a value from one of the WaitEventXXX enums defined in pgstat.h to * wait_event_info should be a value from one of the WaitEventXXX enums
* control the contents of pg_stat_activity's wait_event_type and wait_event * defined in pgstat.h. This controls the contents of pg_stat_activity's
* columns while waiting. * wait_event_type and wait_event columns while waiting.
*-------------------------------------------------------------------------*/ */
void void
ConditionVariableSleep(ConditionVariable *cv, uint32 wait_event_info) ConditionVariableSleep(ConditionVariable *cv, uint32 wait_event_info)
{ {
...@@ -113,13 +125,14 @@ ConditionVariableSleep(ConditionVariable *cv, uint32 wait_event_info) ...@@ -113,13 +125,14 @@ ConditionVariableSleep(ConditionVariable *cv, uint32 wait_event_info)
/* /*
* If the caller didn't prepare to sleep explicitly, then do so now and * If the caller didn't prepare to sleep explicitly, then do so now and
* return immediately. The caller's predicate loop should immediately * return immediately. The caller's predicate loop should immediately
* call again if its exit condition is not yet met. This initial spurious * call again if its exit condition is not yet met. This will result in
* return can be avoided by calling ConditionVariablePrepareToSleep(cv) * the exit condition being tested twice before we first sleep. The extra
* test can be prevented by calling ConditionVariablePrepareToSleep(cv)
* first. Whether it's worth doing that depends on whether you expect the * first. Whether it's worth doing that depends on whether you expect the
* condition to be met initially, in which case skipping the prepare * exit condition to be met initially, in which case skipping the prepare
* allows you to skip manipulation of the wait list, or not met initially, * is recommended because it avoids manipulations of the wait list, or not
* in which case preparing first allows you to skip a spurious test of the * met initially, in which case preparing first is better because it
* caller's exit condition. * avoids one extra test of the exit condition.
*/ */
if (cv_sleep_target == NULL) if (cv_sleep_target == NULL)
{ {
...@@ -130,7 +143,7 @@ ConditionVariableSleep(ConditionVariable *cv, uint32 wait_event_info) ...@@ -130,7 +143,7 @@ ConditionVariableSleep(ConditionVariable *cv, uint32 wait_event_info)
/* Any earlier condition variable sleep must have been canceled. */ /* Any earlier condition variable sleep must have been canceled. */
Assert(cv_sleep_target == cv); Assert(cv_sleep_target == cv);
while (!done) do
{ {
CHECK_FOR_INTERRUPTS(); CHECK_FOR_INTERRUPTS();
...@@ -140,18 +153,23 @@ ConditionVariableSleep(ConditionVariable *cv, uint32 wait_event_info) ...@@ -140,18 +153,23 @@ ConditionVariableSleep(ConditionVariable *cv, uint32 wait_event_info)
*/ */
WaitEventSetWait(cv_wait_event_set, -1, &event, 1, wait_event_info); WaitEventSetWait(cv_wait_event_set, -1, &event, 1, wait_event_info);
/* Reset latch before testing whether we can return. */ /* Reset latch before examining the state of the wait list. */
ResetLatch(MyLatch); ResetLatch(MyLatch);
/* /*
* If this process has been taken out of the wait list, then we know * If this process has been taken out of the wait list, then we know
* that is has been signaled by ConditionVariableSignal. We put it * that it has been signaled by ConditionVariableSignal (or
* back into the wait list, so we don't miss any further signals while * ConditionVariableBroadcast), so we should return to the caller. But
* the caller's loop checks its condition. If it hasn't been taken * that doesn't guarantee that the exit condition is met, only that we
* out of the wait list, then the latch must have been set by * ought to check it. So we must put the process back into the wait
* something other than ConditionVariableSignal; though we don't * list, to ensure we don't miss any additional wakeup occurring while
* guarantee not to return spuriously, we'll avoid these obvious * the caller checks its exit condition. We can take ourselves out of
* cases. * the wait list only when the caller calls
* ConditionVariableCancelSleep.
*
* If we're still in the wait list, then the latch must have been set
* by something other than ConditionVariableSignal; though we don't
* guarantee not to return spuriously, we'll avoid this obvious case.
*/ */
SpinLockAcquire(&cv->mutex); SpinLockAcquire(&cv->mutex);
if (!proclist_contains(&cv->wakeup, MyProc->pgprocno, cvWaitLink)) if (!proclist_contains(&cv->wakeup, MyProc->pgprocno, cvWaitLink))
...@@ -160,13 +178,17 @@ ConditionVariableSleep(ConditionVariable *cv, uint32 wait_event_info) ...@@ -160,13 +178,17 @@ ConditionVariableSleep(ConditionVariable *cv, uint32 wait_event_info)
proclist_push_tail(&cv->wakeup, MyProc->pgprocno, cvWaitLink); proclist_push_tail(&cv->wakeup, MyProc->pgprocno, cvWaitLink);
} }
SpinLockRelease(&cv->mutex); SpinLockRelease(&cv->mutex);
} } while (!done);
} }
/* /*
* Cancel any pending sleep operation. We just need to remove ourselves * Cancel any pending sleep operation.
* from the wait queue of any condition variable for which we have previously *
* prepared a sleep. * We just need to remove ourselves from the wait queue of any condition
* variable for which we have previously prepared a sleep.
*
* Do nothing if nothing is pending; this allows this function to be called
* during transaction abort to clean up any unfinished CV sleep.
*/ */
void void
ConditionVariableCancelSleep(void) ConditionVariableCancelSleep(void)
......
...@@ -27,30 +27,33 @@ ...@@ -27,30 +27,33 @@
typedef struct typedef struct
{ {
slock_t mutex; slock_t mutex; /* spinlock protecting the wakeup list */
proclist_head wakeup; proclist_head wakeup; /* list of wake-able processes */
} ConditionVariable; } ConditionVariable;
/* Initialize a condition variable. */ /* Initialize a condition variable. */
extern void ConditionVariableInit(ConditionVariable *); extern void ConditionVariableInit(ConditionVariable *cv);
/* /*
* To sleep on a condition variable, a process should use a loop which first * To sleep on a condition variable, a process should use a loop which first
* checks the condition, exiting the loop if it is met, and then calls * checks the condition, exiting the loop if it is met, and then calls
* ConditionVariableSleep. Spurious wakeups are possible, but should be * ConditionVariableSleep. Spurious wakeups are possible, but should be
* infrequent. After exiting the loop, ConditionVariableCancelSleep should * infrequent. After exiting the loop, ConditionVariableCancelSleep must
* be called to ensure that the process is no longer in the wait list for * be called to ensure that the process is no longer in the wait list for
* the condition variable. * the condition variable. Only one condition variable can be used at a
* time, ie, ConditionVariableCancelSleep must be called before any attempt
* is made to sleep on a different condition variable.
*/ */
extern void ConditionVariableSleep(ConditionVariable *, uint32 wait_event_info); extern void ConditionVariableSleep(ConditionVariable *cv, uint32 wait_event_info);
extern void ConditionVariableCancelSleep(void); extern void ConditionVariableCancelSleep(void);
/* /*
* The use of this function is optional and not necessary for correctness; * Optionally, ConditionVariablePrepareToSleep can be called before entering
* for efficiency, it should be called prior entering the loop described above * the test-and-sleep loop described above. Doing so is more efficient if
* if it is thought that the condition is unlikely to hold immediately. * at least one sleep is needed, whereas not doing so is more efficient when
* no sleep is needed because the test condition is true the first time.
*/ */
extern void ConditionVariablePrepareToSleep(ConditionVariable *); extern void ConditionVariablePrepareToSleep(ConditionVariable *cv);
/* Wake up a single waiter (via signal) or all waiters (via broadcast). */ /* Wake up a single waiter (via signal) or all waiters (via broadcast). */
extern void ConditionVariableSignal(ConditionVariable *cv); extern void ConditionVariableSignal(ConditionVariable *cv);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment