Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit e8ac886

Browse files
committed
Support condition variables.
Condition variables provide a flexible way to sleep until a cooperating process causes an arbitrary condition to become true. In simple cases, this can be accomplished with a WaitLatch/ResetLatch loop; the cooperating process can call SetLatch after performing work that might cause the condition to be satisfied, and the waiting process can recheck the condition each time. However, if the process performing the work doesn't have an easy way to identify which processes might be waiting, this doesn't work, because it can't identify which latches to set. Condition variables solve that problem by internally maintaining a list of waiters; a process that may have caused some waiter's condition to be satisfied must "signal" or "broadcast" on the condition variable. Robert Haas and Thomas Munro
1 parent 1c7861e commit e8ac886

File tree

12 files changed

+364
-2
lines changed

12 files changed

+364
-2
lines changed

src/backend/access/transam/xact.c

+4
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
#include "replication/origin.h"
4646
#include "replication/syncrep.h"
4747
#include "replication/walsender.h"
48+
#include "storage/condition_variable.h"
4849
#include "storage/fd.h"
4950
#include "storage/lmgr.h"
5051
#include "storage/predicate.h"
@@ -2472,6 +2473,9 @@ AbortTransaction(void)
24722473
/* Reset WAL record construction state */
24732474
XLogResetInsertion();
24742475

2476+
/* Cancel condition variable sleep */
2477+
ConditionVariableCancelSleep();
2478+
24752479
/*
24762480
* Also clean up any open wait for lock, since the lock manager will choke
24772481
* if we try to wait for another lock before doing this.

src/backend/bootstrap/bootstrap.c

+2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include "replication/walreceiver.h"
3434
#include "storage/bufmgr.h"
3535
#include "storage/bufpage.h"
36+
#include "storage/condition_variable.h"
3637
#include "storage/ipc.h"
3738
#include "storage/proc.h"
3839
#include "tcop/tcopprot.h"
@@ -536,6 +537,7 @@ static void
536537
ShutdownAuxiliaryProcess(int code, Datum arg)
537538
{
538539
LWLockReleaseAll();
540+
ConditionVariableCancelSleep();
539541
pgstat_report_wait_end();
540542
}
541543

src/backend/postmaster/bgwriter.c

+2
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
#include "postmaster/bgwriter.h"
4747
#include "storage/bufmgr.h"
4848
#include "storage/buf_internals.h"
49+
#include "storage/condition_variable.h"
4950
#include "storage/fd.h"
5051
#include "storage/ipc.h"
5152
#include "storage/lwlock.h"
@@ -187,6 +188,7 @@ BackgroundWriterMain(void)
187188
* about in bgwriter, but we do have LWLocks, buffers, and temp files.
188189
*/
189190
LWLockReleaseAll();
191+
ConditionVariableCancelSleep();
190192
AbortBufferIO();
191193
UnlockBuffers();
192194
/* buffer pins are released here: */

src/backend/postmaster/checkpointer.c

+2
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
#include "postmaster/bgwriter.h"
5050
#include "replication/syncrep.h"
5151
#include "storage/bufmgr.h"
52+
#include "storage/condition_variable.h"
5253
#include "storage/fd.h"
5354
#include "storage/ipc.h"
5455
#include "storage/lwlock.h"
@@ -271,6 +272,7 @@ CheckpointerMain(void)
271272
* files.
272273
*/
273274
LWLockReleaseAll();
275+
ConditionVariableCancelSleep();
274276
pgstat_report_wait_end();
275277
AbortBufferIO();
276278
UnlockBuffers();

src/backend/postmaster/walwriter.c

+2
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
#include "pgstat.h"
5151
#include "postmaster/walwriter.h"
5252
#include "storage/bufmgr.h"
53+
#include "storage/condition_variable.h"
5354
#include "storage/fd.h"
5455
#include "storage/ipc.h"
5556
#include "storage/lwlock.h"
@@ -167,6 +168,7 @@ WalWriterMain(void)
167168
* about in walwriter, but we do have LWLocks, and perhaps buffers?
168169
*/
169170
LWLockReleaseAll();
171+
ConditionVariableCancelSleep();
170172
pgstat_report_wait_end();
171173
AbortBufferIO();
172174
UnlockBuffers();

src/backend/replication/walsender.c

+2
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
#include "replication/walreceiver.h"
6767
#include "replication/walsender.h"
6868
#include "replication/walsender_private.h"
69+
#include "storage/condition_variable.h"
6970
#include "storage/fd.h"
7071
#include "storage/ipc.h"
7172
#include "storage/pmsignal.h"
@@ -253,6 +254,7 @@ void
253254
WalSndErrorCleanup(void)
254255
{
255256
LWLockReleaseAll();
257+
ConditionVariableCancelSleep();
256258
pgstat_report_wait_end();
257259

258260
if (sendFile >= 0)

src/backend/storage/lmgr/Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ top_builddir = ../../../..
1313
include $(top_builddir)/src/Makefile.global
1414

1515
OBJS = lmgr.o lock.o proc.o deadlock.o lwlock.o lwlocknames.o spin.o \
16-
s_lock.o predicate.o
16+
s_lock.o predicate.o condition_variable.o
1717

1818
include $(top_srcdir)/src/backend/common.mk
1919

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
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+
}

src/backend/storage/lmgr/proc.c

+7
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
#include "postmaster/autovacuum.h"
4444
#include "replication/slot.h"
4545
#include "replication/syncrep.h"
46+
#include "storage/condition_variable.h"
4647
#include "storage/standby.h"
4748
#include "storage/ipc.h"
4849
#include "storage/lmgr.h"
@@ -802,6 +803,9 @@ ProcKill(int code, Datum arg)
802803
*/
803804
LWLockReleaseAll();
804805

806+
/* Cancel any pending condition variable sleep, too */
807+
ConditionVariableCancelSleep();
808+
805809
/* Make sure active replication slots are released */
806810
if (MyReplicationSlot != NULL)
807811
ReplicationSlotRelease();
@@ -907,6 +911,9 @@ AuxiliaryProcKill(int code, Datum arg)
907911
/* Release any LW locks I am holding (see notes above) */
908912
LWLockReleaseAll();
909913

914+
/* Cancel any pending condition variable sleep, too */
915+
ConditionVariableCancelSleep();
916+
910917
/*
911918
* Reset MyLatch to the process local one. This is so that signal
912919
* handlers et al can continue using the latch after the shared latch

0 commit comments

Comments
 (0)