Baton::ready, a const variant of try_wait
[folly.git] / folly / synchronization / Baton.h
1 /*
2  * Copyright 2017 Facebook, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #pragma once
18
19 #include <assert.h>
20 #include <errno.h>
21 #include <stdint.h>
22 #include <atomic>
23 #include <thread>
24
25 #include <folly/Likely.h>
26 #include <folly/detail/Futex.h>
27 #include <folly/detail/MemoryIdler.h>
28 #include <folly/portability/Asm.h>
29
30 namespace folly {
31
32 /// A Baton allows a thread to block once and be awoken. Captures a
33 /// single handoff, and during its lifecycle (from construction/reset
34 /// to destruction/reset) a baton must either be post()ed and wait()ed
35 /// exactly once each, or not at all.
36 ///
37 /// Baton includes no internal padding, and is only 4 bytes in size.
38 /// Any alignment or padding to avoid false sharing is up to the user.
39 ///
40 /// This is basically a stripped-down semaphore that supports only a
41 /// single call to sem_post and a single call to sem_wait.
42 ///
43 /// The non-blocking version (Blocking == false) provides more speed
44 /// by using only load acquire and store release operations in the
45 /// critical path, at the cost of disallowing blocking and timing out.
46 ///
47 /// The current posix semaphore sem_t isn't too bad, but this provides
48 /// more a bit more speed, inlining, smaller size, a guarantee that
49 /// the implementation won't change, and compatibility with
50 /// DeterministicSchedule.  By having a much more restrictive
51 /// lifecycle we can also add a bunch of assertions that can help to
52 /// catch race conditions ahead of time.
53 template <
54     template <typename> class Atom = std::atomic,
55     bool Blocking = true> // blocking vs spinning
56 struct Baton {
57   constexpr Baton() noexcept : state_(INIT) {}
58
59   Baton(Baton const&) = delete;
60   Baton& operator=(Baton const&) = delete;
61
62   /// It is an error to destroy a Baton on which a thread is currently
63   /// wait()ing.  In practice this means that the waiter usually takes
64   /// responsibility for destroying the Baton.
65   ~Baton() noexcept {
66     // The docblock for this function says that it can't be called when
67     // there is a concurrent waiter.  We assume a strong version of this
68     // requirement in which the caller must _know_ that this is true, they
69     // are not allowed to be merely lucky.  If two threads are involved,
70     // the destroying thread must actually have synchronized with the
71     // waiting thread after wait() returned.  To convey causality the the
72     // waiting thread must have used release semantics and the destroying
73     // thread must have used acquire semantics for that communication,
74     // so we are guaranteed to see the post-wait() value of state_,
75     // which cannot be WAITING.
76     //
77     // Note that since we only care about a single memory location,
78     // the only two plausible memory orders here are relaxed and seq_cst.
79     assert(state_.load(std::memory_order_relaxed) != WAITING);
80   }
81
82   FOLLY_ALWAYS_INLINE bool ready() const noexcept {
83     auto s = state_.load(std::memory_order_acquire);
84     assert(s == INIT || s == EARLY_DELIVERY);
85     return LIKELY(s == EARLY_DELIVERY);
86   }
87
88   /// Equivalent to destroying the Baton and creating a new one.  It is
89   /// a bug to call this while there is a waiting thread, so in practice
90   /// the waiter will be the one that resets the baton.
91   void reset() noexcept {
92     // See ~Baton for a discussion about why relaxed is okay here
93     assert(state_.load(std::memory_order_relaxed) != WAITING);
94
95     // We use a similar argument to justify the use of a relaxed store
96     // here.  Since both wait() and post() are required to be called
97     // only once per lifetime, no thread can actually call those methods
98     // correctly after a reset() unless it synchronizes with the thread
99     // that performed the reset().  If a post() or wait() on another thread
100     // didn't synchronize, then regardless of what operation we performed
101     // here there would be a race on proper use of the Baton's spec
102     // (although not on any particular load and store).  Put another way,
103     // we don't need to synchronize here because anybody that might rely
104     // on such synchronization is required by the baton rules to perform
105     // an additional synchronization that has the desired effect anyway.
106     //
107     // There is actually a similar argument to be made about the
108     // constructor, in which the fenceless constructor initialization
109     // of state_ is piggybacked on whatever synchronization mechanism
110     // distributes knowledge of the Baton's existence
111     state_.store(INIT, std::memory_order_relaxed);
112   }
113
114   /// Causes wait() to wake up.  For each lifetime of a Baton (where a
115   /// lifetime starts at construction or reset() and ends at
116   /// destruction or reset()) there can be at most one call to post(),
117   /// in the single poster version.  Any thread may call post().
118   void post() noexcept {
119     if (!Blocking) {
120       /// Non-blocking version
121       ///
122       assert([&] {
123         auto state = state_.load(std::memory_order_relaxed);
124         return (state == INIT || state == EARLY_DELIVERY);
125       }());
126       state_.store(EARLY_DELIVERY, std::memory_order_release);
127       return;
128     }
129
130     /// Blocking versions
131     ///
132     uint32_t before = state_.load(std::memory_order_acquire);
133
134     assert(before == INIT || before == WAITING || before == TIMED_OUT);
135
136     if (before == INIT &&
137         state_.compare_exchange_strong(
138             before,
139             EARLY_DELIVERY,
140             std::memory_order_release,
141             std::memory_order_relaxed)) {
142       return;
143     }
144
145     assert(before == WAITING || before == TIMED_OUT);
146
147     if (before == TIMED_OUT) {
148       return;
149     }
150
151     assert(before == WAITING);
152     state_.store(LATE_DELIVERY, std::memory_order_release);
153     state_.futexWake(1);
154   }
155
156   /// Waits until post() has been called in the current Baton lifetime.
157   /// May be called at most once during a Baton lifetime (construction
158   /// |reset until destruction|reset).  If post is called before wait in
159   /// the current lifetime then this method returns immediately.
160   ///
161   /// The restriction that there can be at most one wait() per lifetime
162   /// could be relaxed somewhat without any perf or size regressions,
163   /// but by making this condition very restrictive we can provide better
164   /// checking in debug builds.
165   FOLLY_ALWAYS_INLINE void wait() noexcept {
166     if (try_wait()) {
167       return;
168     }
169
170     waitSlow();
171   }
172
173   /// Similar to wait, but doesn't block the thread if it hasn't been posted.
174   ///
175   /// try_wait has the following semantics:
176   /// - It is ok to call try_wait any number times on the same baton until
177   ///   try_wait reports that the baton has been posted.
178   /// - It is ok to call timed_wait or wait on the same baton if try_wait
179   ///   reports that baton hasn't been posted.
180   /// - If try_wait indicates that the baton has been posted, it is invalid to
181   ///   call wait, try_wait or timed_wait on the same baton without resetting
182   ///
183   /// @return       true if baton has been posted, false othewise
184   FOLLY_ALWAYS_INLINE bool try_wait() const noexcept {
185     return ready();
186   }
187
188   /// Similar to wait, but with a timeout. The thread is unblocked if the
189   /// timeout expires.
190   /// Note: Only a single call to wait/try_wait_for/try_wait_until is allowed
191   /// during a baton's life-cycle (from ctor/reset to dtor/reset). In other
192   /// words, after try_wait_for the caller can't invoke
193   /// wait/try_wait/try_wait_for/try_wait_until
194   /// again on the same baton without resetting it.
195   ///
196   /// @param  timeout       Time until which the thread can block
197   /// @return               true if the baton was posted to before timeout,
198   ///                       false otherwise
199   template <typename Rep, typename Period>
200   FOLLY_ALWAYS_INLINE bool try_wait_for(
201       const std::chrono::duration<Rep, Period>& timeout) noexcept {
202     static_assert(
203         Blocking, "Non-blocking Baton does not support try_wait_for.");
204
205     if (try_wait()) {
206       return true;
207     }
208
209     auto deadline = std::chrono::steady_clock::now() + timeout;
210     return tryWaitUntilSlow(deadline);
211   }
212
213   /// Similar to wait, but with a deadline. The thread is unblocked if the
214   /// deadline expires.
215   /// Note: Only a single call to wait/try_wait_for/try_wait_until is allowed
216   /// during a baton's life-cycle (from ctor/reset to dtor/reset). In other
217   /// words, after try_wait_until the caller can't invoke
218   /// wait/try_wait/try_wait_for/try_wait_until
219   /// again on the same baton without resetting it.
220   ///
221   /// @param  deadline      Time until which the thread can block
222   /// @return               true if the baton was posted to before deadline,
223   ///                       false otherwise
224   template <typename Clock, typename Duration>
225   FOLLY_ALWAYS_INLINE bool try_wait_until(
226       const std::chrono::time_point<Clock, Duration>& deadline) noexcept {
227     static_assert(
228         Blocking, "Non-blocking Baton does not support try_wait_until.");
229
230     if (try_wait()) {
231       return true;
232     }
233
234     return tryWaitUntilSlow(deadline);
235   }
236
237   /// Alias to try_wait_for. Deprecated.
238   template <typename Rep, typename Period>
239   FOLLY_ALWAYS_INLINE bool timed_wait(
240       const std::chrono::duration<Rep, Period>& timeout) noexcept {
241     return try_wait_for(timeout);
242   }
243
244   /// Alias to try_wait_until. Deprecated.
245   template <typename Clock, typename Duration>
246   FOLLY_ALWAYS_INLINE bool timed_wait(
247       const std::chrono::time_point<Clock, Duration>& deadline) noexcept {
248     return try_wait_until(deadline);
249   }
250
251  private:
252   enum State : uint32_t {
253     INIT = 0,
254     EARLY_DELIVERY = 1,
255     WAITING = 2,
256     LATE_DELIVERY = 3,
257     TIMED_OUT = 4,
258   };
259
260   enum {
261     // Must be positive.  If multiple threads are actively using a
262     // higher-level data structure that uses batons internally, it is
263     // likely that the post() and wait() calls happen almost at the same
264     // time.  In this state, we lose big 50% of the time if the wait goes
265     // to sleep immediately.  On circa-2013 devbox hardware it costs about
266     // 7 usec to FUTEX_WAIT and then be awoken (half the t/iter as the
267     // posix_sem_pingpong test in BatonTests).  We can improve our chances
268     // of EARLY_DELIVERY by spinning for a bit, although we have to balance
269     // this against the loss if we end up sleeping any way.  Spins on this
270     // hw take about 7 nanos (all but 0.5 nanos is the pause instruction).
271     // We give ourself 300 spins, which is about 2 usec of waiting.  As a
272     // partial consolation, since we are using the pause instruction we
273     // are giving a speed boost to the colocated hyperthread.
274     PreBlockAttempts = 300,
275   };
276
277   // Spin for "some time" (see discussion on PreBlockAttempts) waiting
278   // for a post.
279   //
280   // @return       true if we received an early delivery during the wait,
281   //               false otherwise. If the function returns true then
282   //               state_ is guaranteed to be EARLY_DELIVERY
283   bool spinWaitForEarlyDelivery() noexcept {
284     static_assert(
285         PreBlockAttempts > 0,
286         "isn't this assert clearer than an uninitialized variable warning?");
287     for (int i = 0; i < PreBlockAttempts; ++i) {
288       if (try_wait()) {
289         return true;
290       }
291
292       // The pause instruction is the polite way to spin, but it doesn't
293       // actually affect correctness to omit it if we don't have it.
294       // Pausing donates the full capabilities of the current core to
295       // its other hyperthreads for a dozen cycles or so
296       asm_volatile_pause();
297     }
298
299     return false;
300   }
301
302   FOLLY_NOINLINE void waitSlow() noexcept {
303     if (spinWaitForEarlyDelivery()) {
304       assert(state_.load(std::memory_order_acquire) == EARLY_DELIVERY);
305       return;
306     }
307
308     if (!Blocking) {
309       while (!try_wait()) {
310         std::this_thread::yield();
311       }
312       return;
313     }
314
315     // guess we have to block :(
316     uint32_t expected = INIT;
317     if (!state_.compare_exchange_strong(expected, WAITING)) {
318       // CAS failed, last minute reprieve
319       assert(expected == EARLY_DELIVERY);
320       return;
321     }
322
323     while (true) {
324       detail::MemoryIdler::futexWait(state_, WAITING);
325
326       // state_ is the truth even if FUTEX_WAIT reported a matching
327       // FUTEX_WAKE, since we aren't using type-stable storage and we
328       // don't guarantee reuse.  The scenario goes like this: thread
329       // A's last touch of a Baton is a call to wake(), which stores
330       // LATE_DELIVERY and gets an unlucky context switch before delivering
331       // the corresponding futexWake.  Thread B sees LATE_DELIVERY
332       // without consuming a futex event, because it calls futexWait
333       // with an expected value of WAITING and hence doesn't go to sleep.
334       // B returns, so the Baton's memory is reused and becomes another
335       // Baton (or a reuse of this one).  B calls futexWait on the new
336       // Baton lifetime, then A wakes up and delivers a spurious futexWake
337       // to the same memory location.  B's futexWait will then report a
338       // consumed wake event even though state_ is still WAITING.
339       //
340       // It would be possible to add an extra state_ dance to communicate
341       // that the futexWake has been sent so that we can be sure to consume
342       // it before returning, but that would be a perf and complexity hit.
343       uint32_t s = state_.load(std::memory_order_acquire);
344       assert(s == WAITING || s == LATE_DELIVERY);
345
346       if (s == LATE_DELIVERY) {
347         return;
348       }
349       // retry
350     }
351   }
352
353   template <typename Clock, typename Duration>
354   FOLLY_NOINLINE bool tryWaitUntilSlow(
355       const std::chrono::time_point<Clock, Duration>& deadline) noexcept {
356     if (spinWaitForEarlyDelivery()) {
357       assert(state_.load(std::memory_order_acquire) == EARLY_DELIVERY);
358       return true;
359     }
360
361     // guess we have to block :(
362     uint32_t expected = INIT;
363     if (!state_.compare_exchange_strong(expected, WAITING)) {
364       // CAS failed, last minute reprieve
365       assert(expected == EARLY_DELIVERY);
366       return true;
367     }
368
369     while (true) {
370       auto rv = state_.futexWaitUntil(WAITING, deadline);
371       if (rv == folly::detail::FutexResult::TIMEDOUT) {
372         state_.store(TIMED_OUT, std::memory_order_release);
373         return false;
374       }
375
376       uint32_t s = state_.load(std::memory_order_acquire);
377       assert(s == WAITING || s == LATE_DELIVERY);
378       if (s == LATE_DELIVERY) {
379         return true;
380       }
381     }
382   }
383
384   detail::Futex<Atom> state_;
385 };
386
387 } // namespace folly