mirror of
https://github.com/postgres/postgres.git
synced 2025-05-05 09:19:17 +03:00
The larger part of this patch replaces usages of MyProc->procLatch with MyLatch. The latter works even early during backend startup, where MyProc->procLatch doesn't yet. While the affected code shouldn't run in cases where it's not initialized, it might get copied into places where it might. Using MyLatch is simpler and a bit faster to boot, so there's little point to stick with the previous coding. While doing so I noticed some weaknesses around newly introduced uses of latches that could lead to missed events, and an omitted CHECK_FOR_INTERRUPTS() call in worker_spi. As all the actual bugs are in v10 code, there doesn't seem to be sufficient reason to backpatch this. Author: Andres Freund Discussion: https://postgr.es/m/20170606195321.sjmenrfgl2nu6j63@alap3.anarazel.de https://postgr.es/m/20170606210405.sim3yl6vpudhmufo@alap3.anarazel.de Backpatch: -
229 lines
6.9 KiB
C
229 lines
6.9 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* condition_variable.c
|
|
* Implementation of condition variables. Condition variables provide
|
|
* a way for one process to wait until a specific condition occurs,
|
|
* without needing to know the specific identity of the process for
|
|
* which they are waiting. Waits for condition variables can be
|
|
* interrupted, unlike LWLock waits. Condition variables are safe
|
|
* to use within dynamic shared memory segments.
|
|
*
|
|
* Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
* src/backend/storage/lmgr/condition_variable.c
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
#include "miscadmin.h"
|
|
#include "storage/condition_variable.h"
|
|
#include "storage/ipc.h"
|
|
#include "storage/proc.h"
|
|
#include "storage/proclist.h"
|
|
#include "storage/spin.h"
|
|
#include "utils/memutils.h"
|
|
|
|
/* Initially, we are not prepared to sleep on any condition variable. */
|
|
static ConditionVariable *cv_sleep_target = NULL;
|
|
|
|
/* Reusable WaitEventSet. */
|
|
static WaitEventSet *cv_wait_event_set = NULL;
|
|
|
|
/*
|
|
* Initialize a condition variable.
|
|
*/
|
|
void
|
|
ConditionVariableInit(ConditionVariable *cv)
|
|
{
|
|
SpinLockInit(&cv->mutex);
|
|
proclist_init(&cv->wakeup);
|
|
}
|
|
|
|
/*
|
|
* Prepare to wait on a given condition variable. This can optionally be
|
|
* called before entering a test/sleep loop. Alternatively, the call to
|
|
* ConditionVariablePrepareToSleep can be omitted. The only advantage of
|
|
* calling ConditionVariablePrepareToSleep is that it avoids an initial
|
|
* double-test of the user's predicate in the case that we need to wait.
|
|
*/
|
|
void
|
|
ConditionVariablePrepareToSleep(ConditionVariable *cv)
|
|
{
|
|
int pgprocno = MyProc->pgprocno;
|
|
|
|
/*
|
|
* It's not legal to prepare a sleep until the previous sleep has been
|
|
* completed or canceled.
|
|
*/
|
|
Assert(cv_sleep_target == NULL);
|
|
|
|
/* Record the condition variable on which we will sleep. */
|
|
cv_sleep_target = cv;
|
|
|
|
/* Create a reusable WaitEventSet. */
|
|
if (cv_wait_event_set == NULL)
|
|
{
|
|
cv_wait_event_set = CreateWaitEventSet(TopMemoryContext, 1);
|
|
AddWaitEventToSet(cv_wait_event_set, WL_LATCH_SET, PGINVALID_SOCKET,
|
|
MyLatch, NULL);
|
|
}
|
|
|
|
/*
|
|
* Reset my latch before adding myself to the queue and before entering
|
|
* the caller's predicate loop.
|
|
*/
|
|
ResetLatch(MyLatch);
|
|
|
|
/* Add myself to the wait queue. */
|
|
SpinLockAcquire(&cv->mutex);
|
|
if (!proclist_contains(&cv->wakeup, pgprocno, cvWaitLink))
|
|
proclist_push_tail(&cv->wakeup, pgprocno, cvWaitLink);
|
|
SpinLockRelease(&cv->mutex);
|
|
}
|
|
|
|
/*--------------------------------------------------------------------------
|
|
* Wait for the given condition variable to be signaled. This should be
|
|
* called in a predicate loop that tests for a specific exit condition and
|
|
* otherwise sleeps, like so:
|
|
*
|
|
* ConditionVariablePrepareToSleep(cv); [optional]
|
|
* while (condition for which we are waiting is not true)
|
|
* ConditionVariableSleep(cv, wait_event_info);
|
|
* ConditionVariableCancelSleep();
|
|
*
|
|
* Supply a value from one of the WaitEventXXX enums defined in pgstat.h to
|
|
* control the contents of pg_stat_activity's wait_event_type and wait_event
|
|
* columns while waiting.
|
|
*-------------------------------------------------------------------------*/
|
|
void
|
|
ConditionVariableSleep(ConditionVariable *cv, uint32 wait_event_info)
|
|
{
|
|
WaitEvent event;
|
|
bool done = false;
|
|
|
|
/*
|
|
* If the caller didn't prepare to sleep explicitly, then do so now and
|
|
* return immediately. The caller's predicate loop should immediately
|
|
* call again if its exit condition is not yet met. This initial spurious
|
|
* return can be avoided by calling ConditionVariablePrepareToSleep(cv)
|
|
* first. Whether it's worth doing that depends on whether you expect the
|
|
* condition to be met initially, in which case skipping the prepare
|
|
* allows you to skip manipulation of the wait list, or not met initially,
|
|
* in which case preparing first allows you to skip a spurious test of the
|
|
* caller's exit condition.
|
|
*/
|
|
if (cv_sleep_target == NULL)
|
|
{
|
|
ConditionVariablePrepareToSleep(cv);
|
|
return;
|
|
}
|
|
|
|
/* Any earlier condition variable sleep must have been canceled. */
|
|
Assert(cv_sleep_target == cv);
|
|
|
|
while (!done)
|
|
{
|
|
CHECK_FOR_INTERRUPTS();
|
|
|
|
/*
|
|
* Wait for latch to be set. We don't care about the result because
|
|
* our contract permits spurious returns.
|
|
*/
|
|
WaitEventSetWait(cv_wait_event_set, -1, &event, 1, wait_event_info);
|
|
|
|
/* Reset latch before testing whether we can return. */
|
|
ResetLatch(MyLatch);
|
|
|
|
/*
|
|
* If this process has been taken out of the wait list, then we know
|
|
* that is has been signaled by ConditionVariableSignal. We put it
|
|
* back into the wait list, so we don't miss any further signals while
|
|
* the caller's loop checks its condition. If it hasn't been taken
|
|
* out of 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 these obvious
|
|
* cases.
|
|
*/
|
|
SpinLockAcquire(&cv->mutex);
|
|
if (!proclist_contains(&cv->wakeup, MyProc->pgprocno, cvWaitLink))
|
|
{
|
|
done = true;
|
|
proclist_push_tail(&cv->wakeup, MyProc->pgprocno, cvWaitLink);
|
|
}
|
|
SpinLockRelease(&cv->mutex);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Cancel any pending sleep operation. We just need to remove ourselves
|
|
* from the wait queue of any condition variable for which we have previously
|
|
* prepared a sleep.
|
|
*/
|
|
void
|
|
ConditionVariableCancelSleep(void)
|
|
{
|
|
ConditionVariable *cv = cv_sleep_target;
|
|
|
|
if (cv == NULL)
|
|
return;
|
|
|
|
SpinLockAcquire(&cv->mutex);
|
|
if (proclist_contains(&cv->wakeup, MyProc->pgprocno, cvWaitLink))
|
|
proclist_delete(&cv->wakeup, MyProc->pgprocno, cvWaitLink);
|
|
SpinLockRelease(&cv->mutex);
|
|
|
|
cv_sleep_target = NULL;
|
|
}
|
|
|
|
/*
|
|
* Wake up one sleeping process, assuming there is at least one.
|
|
*
|
|
* The return value indicates whether or not we woke somebody up.
|
|
*/
|
|
bool
|
|
ConditionVariableSignal(ConditionVariable *cv)
|
|
{
|
|
PGPROC *proc = NULL;
|
|
|
|
/* Remove the first process from the wakeup queue (if any). */
|
|
SpinLockAcquire(&cv->mutex);
|
|
if (!proclist_is_empty(&cv->wakeup))
|
|
proc = proclist_pop_head_node(&cv->wakeup, cvWaitLink);
|
|
SpinLockRelease(&cv->mutex);
|
|
|
|
/* If we found someone sleeping, set their latch to wake them up. */
|
|
if (proc != NULL)
|
|
{
|
|
SetLatch(&proc->procLatch);
|
|
return true;
|
|
}
|
|
|
|
/* No sleeping processes. */
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Wake up all sleeping processes.
|
|
*
|
|
* The return value indicates the number of processes we woke.
|
|
*/
|
|
int
|
|
ConditionVariableBroadcast(ConditionVariable *cv)
|
|
{
|
|
int nwoken = 0;
|
|
|
|
/*
|
|
* Let's just do this the dumbest way possible. We could try to dequeue
|
|
* all the sleepers at once to save spinlock cycles, but it's a bit hard
|
|
* to get that right in the face of possible sleep cancelations, and we
|
|
* don't want to loop holding the mutex.
|
|
*/
|
|
while (ConditionVariableSignal(cv))
|
|
++nwoken;
|
|
|
|
return nwoken;
|
|
}
|