Timed wait operations for spin-only Baton
[folly.git] / folly / synchronization / Baton.h
index 379f9021f646e939e4cdc485491f3b37c537ba3f..b3b1e09ec3f2070fadd01a09535f612b3fe41d60 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2017 Facebook, Inc.
+ * Copyright 2014-present Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -40,9 +40,9 @@ namespace folly {
 /// This is basically a stripped-down semaphore that supports only a
 /// single call to sem_post and a single call to sem_wait.
 ///
-/// The non-blocking version (Blocking == false) provides more speed
+/// The non-blocking version (MayBlock == false) provides more speed
 /// by using only load acquire and store release operations in the
-/// critical path, at the cost of disallowing blocking and timing out.
+/// critical path, at the cost of disallowing blocking.
 ///
 /// The current posix semaphore sem_t isn't too bad, but this provides
 /// more a bit more speed, inlining, smaller size, a guarantee that
@@ -50,11 +50,9 @@ namespace folly {
 /// DeterministicSchedule.  By having a much more restrictive
 /// lifecycle we can also add a bunch of assertions that can help to
 /// catch race conditions ahead of time.
-template <
-    template <typename> class Atom = std::atomic,
-    bool Blocking = true> // blocking vs spinning
+template <bool MayBlock = true, template <typename> class Atom = std::atomic>
 struct Baton {
-  constexpr Baton() : state_(INIT) {}
+  constexpr Baton() noexcept : state_(INIT) {}
 
   Baton(Baton const&) = delete;
   Baton& operator=(Baton const&) = delete;
@@ -62,7 +60,7 @@ struct Baton {
   /// It is an error to destroy a Baton on which a thread is currently
   /// wait()ing.  In practice this means that the waiter usually takes
   /// responsibility for destroying the Baton.
-  ~Baton() {
+  ~Baton() noexcept {
     // The docblock for this function says that it can't be called when
     // there is a concurrent waiter.  We assume a strong version of this
     // requirement in which the caller must _know_ that this is true, they
@@ -79,10 +77,16 @@ struct Baton {
     assert(state_.load(std::memory_order_relaxed) != WAITING);
   }
 
+  FOLLY_ALWAYS_INLINE bool ready() const noexcept {
+    auto s = state_.load(std::memory_order_acquire);
+    assert(s == INIT || s == EARLY_DELIVERY);
+    return LIKELY(s == EARLY_DELIVERY);
+  }
+
   /// Equivalent to destroying the Baton and creating a new one.  It is
   /// a bug to call this while there is a waiting thread, so in practice
   /// the waiter will be the one that resets the baton.
-  void reset() {
+  void reset() noexcept {
     // See ~Baton for a discussion about why relaxed is okay here
     assert(state_.load(std::memory_order_relaxed) != WAITING);
 
@@ -109,9 +113,9 @@ struct Baton {
   /// lifetime starts at construction or reset() and ends at
   /// destruction or reset()) there can be at most one call to post(),
   /// in the single poster version.  Any thread may call post().
-  void post() {
-    if (!Blocking) {
-      /// Non-blocking version
+  void post() noexcept {
+    if (!MayBlock) {
+      /// Spin-only version
       ///
       assert([&] {
         auto state = state_.load(std::memory_order_relaxed);
@@ -121,14 +125,18 @@ struct Baton {
       return;
     }
 
-    /// Blocking versions
+    /// May-block versions
     ///
     uint32_t before = state_.load(std::memory_order_acquire);
 
     assert(before == INIT || before == WAITING || before == TIMED_OUT);
 
     if (before == INIT &&
-        state_.compare_exchange_strong(before, EARLY_DELIVERY)) {
+        state_.compare_exchange_strong(
+            before,
+            EARLY_DELIVERY,
+            std::memory_order_release,
+            std::memory_order_relaxed)) {
       return;
     }
 
@@ -152,7 +160,7 @@ struct Baton {
   /// could be relaxed somewhat without any perf or size regressions,
   /// but by making this condition very restrictive we can provide better
   /// checking in debug builds.
-  FOLLY_ALWAYS_INLINE void wait() {
+  FOLLY_ALWAYS_INLINE void wait() noexcept {
     if (try_wait()) {
       return;
     }
@@ -171,10 +179,8 @@ struct Baton {
   ///   call wait, try_wait or timed_wait on the same baton without resetting
   ///
   /// @return       true if baton has been posted, false othewise
-  FOLLY_ALWAYS_INLINE bool try_wait() const {
-    auto s = state_.load(std::memory_order_acquire);
-    assert(s == INIT || s == EARLY_DELIVERY);
-    return LIKELY(s == EARLY_DELIVERY);
+  FOLLY_ALWAYS_INLINE bool try_wait() const noexcept {
+    return ready();
   }
 
   /// Similar to wait, but with a timeout. The thread is unblocked if the
@@ -190,10 +196,7 @@ struct Baton {
   ///                       false otherwise
   template <typename Rep, typename Period>
   FOLLY_ALWAYS_INLINE bool try_wait_for(
-      const std::chrono::duration<Rep, Period>& timeout) {
-    static_assert(
-        Blocking, "Non-blocking Baton does not support try_wait_for.");
-
+      const std::chrono::duration<Rep, Period>& timeout) noexcept {
     if (try_wait()) {
       return true;
     }
@@ -215,10 +218,7 @@ struct Baton {
   ///                       false otherwise
   template <typename Clock, typename Duration>
   FOLLY_ALWAYS_INLINE bool try_wait_until(
-      const std::chrono::time_point<Clock, Duration>& deadline) {
-    static_assert(
-        Blocking, "Non-blocking Baton does not support try_wait_until.");
-
+      const std::chrono::time_point<Clock, Duration>& deadline) noexcept {
     if (try_wait()) {
       return true;
     }
@@ -229,14 +229,14 @@ struct Baton {
   /// Alias to try_wait_for. Deprecated.
   template <typename Rep, typename Period>
   FOLLY_ALWAYS_INLINE bool timed_wait(
-      const std::chrono::duration<Rep, Period>& timeout) {
+      const std::chrono::duration<Rep, Period>& timeout) noexcept {
     return try_wait_for(timeout);
   }
 
   /// Alias to try_wait_until. Deprecated.
   template <typename Clock, typename Duration>
   FOLLY_ALWAYS_INLINE bool timed_wait(
-      const std::chrono::time_point<Clock, Duration>& deadline) {
+      const std::chrono::time_point<Clock, Duration>& deadline) noexcept {
     return try_wait_until(deadline);
   }
 
@@ -272,15 +272,16 @@ struct Baton {
   // @return       true if we received an early delivery during the wait,
   //               false otherwise. If the function returns true then
   //               state_ is guaranteed to be EARLY_DELIVERY
-  bool spinWaitForEarlyDelivery() {
-    static_assert(
-        PreBlockAttempts > 0,
-        "isn't this assert clearer than an uninitialized variable warning?");
+  template <typename Clock, typename Duration>
+  bool spinWaitForEarlyDelivery(
+      const std::chrono::time_point<Clock, Duration>& deadline) noexcept {
     for (int i = 0; i < PreBlockAttempts; ++i) {
       if (try_wait()) {
         return true;
       }
-
+      if (Clock::now() >= deadline) {
+        return false;
+      }
       // The pause instruction is the polite way to spin, but it doesn't
       // actually affect correctness to omit it if we don't have it.
       // Pausing donates the full capabilities of the current core to
@@ -291,13 +292,14 @@ struct Baton {
     return false;
   }
 
-  FOLLY_NOINLINE void waitSlow() {
-    if (spinWaitForEarlyDelivery()) {
+  FOLLY_NOINLINE void waitSlow() noexcept {
+    auto const deadline = std::chrono::steady_clock::time_point::max();
+    if (spinWaitForEarlyDelivery(deadline)) {
       assert(state_.load(std::memory_order_acquire) == EARLY_DELIVERY);
       return;
     }
 
-    if (!Blocking) {
+    if (!MayBlock) {
       while (!try_wait()) {
         std::this_thread::yield();
       }
@@ -344,12 +346,24 @@ struct Baton {
 
   template <typename Clock, typename Duration>
   FOLLY_NOINLINE bool tryWaitUntilSlow(
-      const std::chrono::time_point<Clock, Duration>& deadline) {
-    if (spinWaitForEarlyDelivery()) {
+      const std::chrono::time_point<Clock, Duration>& deadline) noexcept {
+    if (spinWaitForEarlyDelivery(deadline)) {
       assert(state_.load(std::memory_order_acquire) == EARLY_DELIVERY);
       return true;
     }
 
+    if (!MayBlock) {
+      while (true) {
+        if (try_wait()) {
+          return true;
+        }
+        if (Clock::now() >= deadline) {
+          return false;
+        }
+        std::this_thread::yield();
+      }
+    }
+
     // guess we have to block :(
     uint32_t expected = INIT;
     if (!state_.compare_exchange_strong(expected, WAITING)) {