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