MemoryIdler::futexWaitUntil
[folly.git] / folly / detail / MemoryIdler.h
index caac253e014be4bd99a749f74f51a90a85daa216..14018f6e006311e0142d03d5a273c6d4ad41fa9a 100644 (file)
@@ -67,74 +67,143 @@ struct MemoryIdler {
   /// Selects a timeout pseudo-randomly chosen to be between
   /// idleTimeout and idleTimeout * (1 + timeoutVariationFraction), to
   /// smooth out the behavior in a bursty system
-  template <typename Clock = std::chrono::steady_clock>
-  static typename Clock::duration getVariationTimeout(
-      typename Clock::duration idleTimeout
-          defaultIdleTimeout.load(std::memory_order_acquire),
+  template <typename IdleTime = std::chrono::steady_clock::duration>
+  static IdleTime getVariationTimeout(
+      IdleTime const& idleTimeout =
+          defaultIdleTimeout.load(std::memory_order_acquire),
       float timeoutVariationFrac = 0.5) {
-    if (idleTimeout.count() > 0 && timeoutVariationFrac > 0) {
-      // hash the pthread_t and the time to get the adjustment.
-      // Standard hash func isn't very good, so bit mix the result
-      auto pr = std::make_pair(getCurrentThreadID(),
-                               Clock::now().time_since_epoch().count());
-      std::hash<decltype(pr)> hash_fn;
-      uint64_t h = folly::hash::twang_mix64(hash_fn(pr));
-
-      // multiplying the duration by a floating point doesn't work, grr..
-      auto extraFrac =
-        timeoutVariationFrac / std::numeric_limits<uint64_t>::max() * h;
-      auto tics = uint64_t(idleTimeout.count() * (1 + extraFrac));
-      idleTimeout = typename Clock::duration(tics);
+    if (idleTimeout <= IdleTime::zero() || timeoutVariationFrac <= 0) {
+      return idleTimeout;
     }
 
-    return idleTimeout;
+    // hash the pthread_t and the time to get the adjustment
+    // Standard hash func isn't very good, so bit mix the result
+    uint64_t h = folly::hash::twang_mix64(folly::hash::hash_combine(
+        getCurrentThreadID(),
+        std::chrono::system_clock::now().time_since_epoch().count()));
+
+    // multiplying the duration by a floating point doesn't work, grr
+    auto extraFrac =
+        timeoutVariationFrac / std::numeric_limits<uint64_t>::max() * h;
+    auto tics = uint64_t(idleTimeout.count() * (1 + extraFrac));
+    return IdleTime(tics);
   }
 
   /// Equivalent to fut.futexWait(expected, waitMask), but calls
   /// flushLocalMallocCaches() and unmapUnusedStack(stackToRetain)
-  /// after idleTimeout has passed (if it has passed).  Internally uses
-  /// fut.futexWait and fut.futexWaitUntil.  Like futexWait, returns
-  /// false if interrupted with a signal.  The actual timeout will be
+  /// after idleTimeout has passed (if it has passed). Internally uses
+  /// fut.futexWait and fut.futexWaitUntil. The actual timeout will be
   /// pseudo-randomly chosen to be between idleTimeout and idleTimeout *
   /// (1 + timeoutVariationFraction), to smooth out the behavior in a
-  /// system with bursty requests.  The default is to wait up to 50%
-  /// extra, so on average 25% extra
+  /// system with bursty requests. The default is to wait up to 50%
+  /// extra, so on average 25% extra.
   template <
       template <typename> class Atom,
-      typename Clock = std::chrono::steady_clock>
+      typename IdleTime = std::chrono::steady_clock::duration>
   static FutexResult futexWait(
       Futex<Atom>& fut,
       uint32_t expected,
       uint32_t waitMask = -1,
-      typename Clock::duration idleTimeout =
+      IdleTime const& idleTimeout =
+          defaultIdleTimeout.load(std::memory_order_acquire),
+      size_t stackToRetain = kDefaultStackToRetain,
+      float timeoutVariationFrac = 0.5) {
+    FutexResult pre;
+    if (futexWaitPreIdle(
+            pre,
+            fut,
+            expected,
+            std::chrono::steady_clock::time_point::max(),
+            waitMask,
+            idleTimeout,
+            stackToRetain,
+            timeoutVariationFrac)) {
+      return pre;
+    }
+    return fut.futexWait(expected, waitMask);
+  }
+
+  /// Equivalent to fut.futexWaitUntil(expected, deadline, waitMask), but
+  /// calls flushLocalMallocCaches() and unmapUnusedStack(stackToRetain)
+  /// after idleTimeout has passed (if it has passed). Internally uses
+  /// fut.futexWaitUntil. The actual timeout will be pseudo-randomly
+  /// chosen to be between idleTimeout and idleTimeout *
+  /// (1 + timeoutVariationFraction), to smooth out the behavior in a
+  /// system with bursty requests. The default is to wait up to 50%
+  /// extra, so on average 25% extra.
+  template <
+      template <typename> class Atom,
+      typename Deadline,
+      typename IdleTime = std::chrono::steady_clock::duration>
+  static FutexResult futexWaitUntil(
+      Futex<Atom>& fut,
+      uint32_t expected,
+      Deadline const& deadline,
+      uint32_t waitMask = -1,
+      IdleTime const& idleTimeout =
           defaultIdleTimeout.load(std::memory_order_acquire),
       size_t stackToRetain = kDefaultStackToRetain,
       float timeoutVariationFrac = 0.5) {
-    if (idleTimeout == Clock::duration::max()) {
-      // no need to use futexWaitUntil if no timeout is possible
-      return fut.futexWait(expected, waitMask);
+    FutexResult pre;
+    if (futexWaitPreIdle(
+            pre,
+            fut,
+            expected,
+            deadline,
+            waitMask,
+            idleTimeout,
+            stackToRetain,
+            timeoutVariationFrac)) {
+      return pre;
     }
+    return fut.futexWaitUntil(expected, deadline, waitMask);
+  }
 
-    idleTimeout = getVariationTimeout(idleTimeout, timeoutVariationFrac);
-    if (idleTimeout.count() > 0) {
-      while (true) {
-        auto rv = fut.futexWaitUntil(
-          expected, Clock::now() + idleTimeout, waitMask);
-        if (rv == FutexResult::TIMEDOUT) {
-          // timeout is over
-          break;
+ private:
+  template <
+      template <typename> class Atom,
+      typename Deadline,
+      typename IdleTime>
+  static bool futexWaitPreIdle(
+      FutexResult& _ret,
+      Futex<Atom>& fut,
+      uint32_t expected,
+      Deadline const& deadline,
+      uint32_t waitMask,
+      IdleTime idleTimeout,
+      size_t stackToRetain,
+      float timeoutVariationFrac) {
+    // idleTimeout < 0 means no flush behavior
+    if (idleTimeout < IdleTime::zero()) {
+      return false;
+    }
+
+    // idleTimeout == 0 means flush immediately, without variation
+    // idleTimeout > 0 means flush after delay, with variation
+    if (idleTimeout > IdleTime::zero()) {
+      idleTimeout = std::max(
+          IdleTime::zero(),
+          getVariationTimeout(idleTimeout, timeoutVariationFrac));
+    }
+    if (idleTimeout > IdleTime::zero()) {
+      auto idleDeadline = Deadline::clock::now() + idleTimeout;
+      if (idleDeadline < deadline) {
+        while (true) {
+          auto rv = fut.futexWaitUntil(expected, idleDeadline, waitMask);
+          if (rv == FutexResult::TIMEDOUT) {
+            break;
+          }
+          // finished before timeout hit, no flush
+          _ret = rv;
+          return true;
         }
-        // finished before timeout hit, no flush
-        assert(rv == FutexResult::VALUE_CHANGED || rv == FutexResult::AWOKEN ||
-               rv == FutexResult::INTERRUPTED);
-        return rv;
       }
     }
 
-    // flush, then wait with no timeout
+    // flush, then wait
     flushLocalMallocCaches();
     unmapUnusedStack(stackToRetain);
-    return fut.futexWait(expected, waitMask);
+    return false;
   }
 };