]> granicus.if.org Git - postgresql/blob - src/backend/storage/lmgr/condition_variable.c
6f1ef0b7e5518bad55f4af1e4849699c4fc7edbe
[postgresql] / src / backend / storage / lmgr / condition_variable.c
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 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         /*
75          * Reset my latch before adding myself to the queue and before entering
76          * the caller's predicate loop.
77          */
78         ResetLatch(&MyProc->procLatch);
79
80         /* Add myself to the wait queue. */
81         SpinLockAcquire(&cv->mutex);
82         if (!proclist_contains(&cv->wakeup, pgprocno, cvWaitLink))
83                 proclist_push_tail(&cv->wakeup, pgprocno, cvWaitLink);
84         SpinLockRelease(&cv->mutex);
85 }
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 ConditionVariableSleep(ConditionVariable *cv, uint32 wait_event_info)
103 {
104         WaitEvent event;
105         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         if (cv_sleep_target == NULL)
119         {
120                 ConditionVariablePrepareToSleep(cv);
121                 return;
122         }
123
124         /* Any earlier condition variable sleep must have been canceled. */
125         Assert(cv_sleep_target == cv);
126
127         while (!done)
128         {
129                 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                 WaitEventSetWait(cv_wait_event_set, -1, &event, 1, wait_event_info);
136
137                 /* Reset latch before testing whether we can return. */
138                 ResetLatch(&MyProc->procLatch);
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                 SpinLockAcquire(&cv->mutex);
151                 if (!proclist_contains(&cv->wakeup, MyProc->pgprocno, cvWaitLink))
152                 {
153                         done = true;
154                         proclist_push_tail(&cv->wakeup, MyProc->pgprocno, cvWaitLink);
155                 }
156                 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 ConditionVariableCancelSleep(void)
167 {
168         ConditionVariable *cv = cv_sleep_target;
169
170         if (cv == NULL)
171                 return;
172
173         SpinLockAcquire(&cv->mutex);
174         if (proclist_contains(&cv->wakeup, MyProc->pgprocno, cvWaitLink))
175                 proclist_delete(&cv->wakeup, MyProc->pgprocno, cvWaitLink);
176         SpinLockRelease(&cv->mutex);
177
178         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 ConditionVariableSignal(ConditionVariable *cv)
188 {
189         PGPROC  *proc = NULL;
190
191         /* Remove the first process from the wakeup queue (if any). */
192         SpinLockAcquire(&cv->mutex);
193         if (!proclist_is_empty(&cv->wakeup))
194                 proc = proclist_pop_head_node(&cv->wakeup, cvWaitLink);
195         SpinLockRelease(&cv->mutex);
196
197         /* If we found someone sleeping, set their latch to wake them up. */
198         if (proc != NULL)
199         {
200                 SetLatch(&proc->procLatch);
201                 return true;
202         }
203
204         /* No sleeping processes. */
205         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 ConditionVariableBroadcast(ConditionVariable *cv)
215 {
216         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
222          * we don't want to loop holding the mutex.
223          */
224         while (ConditionVariableSignal(cv))
225                 ++nwoken;
226
227         return nwoken;
228 }