MemoryIdler::futexWaitUntil
authorYedidya Feldblum <yfeldblum@fb.com>
Thu, 11 Jan 2018 21:26:45 +0000 (13:26 -0800)
committerFacebook Github Bot <facebook-github-bot@users.noreply.github.com>
Thu, 11 Jan 2018 21:41:43 +0000 (13:41 -0800)
Summary:
[Folly] `MemoryIdler::futexWaitUntil`.

Adds `MemoryIdler::futexWaitUntil` that works like `Futex::futexWaitUntil`, in a similar way that `MemoryIdler::futexWait` works like `Futex::futexWait`.

Removes the ability to customize the idle-timeout clock for `MemoryIdler::futexWait` as a side-effect; the idle-timeout is now a pure duration. Now, the clock used with the idle-timeout is the same as the normal deadline clock, so the idle-timeout clock can be set for `MemoryIdler::futexWaitUntil` by providing a deadline with that clock type. This normally would not matter, but it affects the unit-tests.

Reviewed By: djwatson

Differential Revision: D6681679

fbshipit-source-id: e3cf6e71d7530c5877a834b318b423eb91f71eb9

folly/detail/MemoryIdler.h
folly/test/MemoryIdlerTest.cpp

index caac253..14018f6 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;
   }
 };
 
index 6945f22..d23be51 100644 (file)
@@ -64,10 +64,10 @@ struct MockAtom : public std::atomic<T> {
 /// extending its scope beyond that of the test.  I generally avoid
 /// shared_ptr, but a weak_ptr is just the ticket here
 struct MockClock {
-  typedef std::chrono::steady_clock::duration duration;
-  typedef std::chrono::steady_clock::time_point time_point;
+  using duration = std::chrono::steady_clock::duration;
+  using time_point = std::chrono::time_point<MockClock, duration>;
 
-  MOCK_METHOD0(nowImpl, time_point(void));
+  MOCK_METHOD0(nowImpl, time_point());
 
   /// Hold on to the returned shared_ptr until the end of the test
   static std::shared_ptr<StrictMock<MockClock>> setup() {
@@ -103,6 +103,8 @@ struct Futex<MockAtom> {
 } // namespace detail
 } // namespace folly
 
+static auto const forever = MockClock::time_point::max();
+
 TEST(MemoryIdler, futexWaitValueChangedEarly) {
   StrictMock<Futex<MockAtom>> fut;
   auto clock = MockClock::setup();
@@ -115,8 +117,7 @@ TEST(MemoryIdler, futexWaitValueChangedEarly) {
                                            Lt(begin + 2 * idleTimeout)), -1))
       .WillOnce(Return(FutexResult::VALUE_CHANGED));
   EXPECT_EQ(
-      FutexResult::VALUE_CHANGED,
-      (MemoryIdler::futexWait<MockAtom, MockClock>(fut, 1)));
+      FutexResult::VALUE_CHANGED, MemoryIdler::futexWaitUntil(fut, 1, forever));
 }
 
 TEST(MemoryIdler, futexWaitValueChangedLate) {
@@ -130,11 +131,10 @@ TEST(MemoryIdler, futexWaitValueChangedLate) {
   EXPECT_CALL(fut, futexWaitUntil(1, AllOf(Ge(begin + idleTimeout),
                                            Lt(begin + 2 * idleTimeout)), -1))
       .WillOnce(Return(FutexResult::TIMEDOUT));
-  EXPECT_CALL(fut, futexWait(1, -1))
+  EXPECT_CALL(fut, futexWaitUntil(1, forever, -1))
       .WillOnce(Return(FutexResult::VALUE_CHANGED));
   EXPECT_EQ(
-      FutexResult::VALUE_CHANGED,
-      (MemoryIdler::futexWait<MockAtom, MockClock>(fut, 1)));
+      FutexResult::VALUE_CHANGED, MemoryIdler::futexWaitUntil(fut, 1, forever));
 }
 
 TEST(MemoryIdler, futexWaitAwokenEarly) {
@@ -147,9 +147,7 @@ TEST(MemoryIdler, futexWaitAwokenEarly) {
       .WillOnce(Return(begin));
   EXPECT_CALL(fut, futexWaitUntil(1, Ge(begin + idleTimeout), -1))
       .WillOnce(Return(FutexResult::AWOKEN));
-  EXPECT_EQ(
-      FutexResult::AWOKEN,
-      (MemoryIdler::futexWait<MockAtom, MockClock>(fut, 1)));
+  EXPECT_EQ(FutexResult::AWOKEN, MemoryIdler::futexWaitUntil(fut, 1, forever));
 }
 
 TEST(MemoryIdler, futexWaitAwokenLate) {
@@ -162,31 +160,33 @@ TEST(MemoryIdler, futexWaitAwokenLate) {
       .WillOnce(Return(begin));
   EXPECT_CALL(fut, futexWaitUntil(1, begin + idleTimeout, -1))
       .WillOnce(Return(FutexResult::TIMEDOUT));
-  EXPECT_CALL(fut, futexWait(1, -1)).WillOnce(Return(FutexResult::AWOKEN));
+  EXPECT_CALL(fut, futexWaitUntil(1, forever, -1))
+      .WillOnce(Return(FutexResult::AWOKEN));
   EXPECT_EQ(
       FutexResult::AWOKEN,
-      (MemoryIdler::futexWait<MockAtom, MockClock>(
-          fut, 1, -1, idleTimeout, 100, 0.0f)));
+      MemoryIdler::futexWaitUntil(fut, 1, forever, -1, idleTimeout, 100, 0.0f));
 }
 
 TEST(MemoryIdler, futexWaitImmediateFlush) {
   StrictMock<Futex<MockAtom>> fut;
   auto clock = MockClock::setup();
 
-  EXPECT_CALL(fut, futexWait(2, 0xff)).WillOnce(Return(FutexResult::AWOKEN));
+  EXPECT_CALL(fut, futexWaitUntil(2, forever, 0xff))
+      .WillOnce(Return(FutexResult::AWOKEN));
   EXPECT_EQ(
       FutexResult::AWOKEN,
-      (MemoryIdler::futexWait<MockAtom, MockClock>(
-          fut, 2, 0xff, std::chrono::seconds(0))));
+      MemoryIdler::futexWaitUntil(
+          fut, 2, forever, 0xff, std::chrono::seconds(0)));
 }
 
 TEST(MemoryIdler, futexWaitNeverFlush) {
   StrictMock<Futex<MockAtom>> fut;
   auto clock = MockClock::setup();
 
-  EXPECT_CALL(fut, futexWait(1, -1)).WillOnce(Return(FutexResult::AWOKEN));
+  EXPECT_CALL(fut, futexWaitUntil(1, forever, -1))
+      .WillOnce(Return(FutexResult::AWOKEN));
   EXPECT_EQ(
       FutexResult::AWOKEN,
-      (MemoryIdler::futexWait<MockAtom, MockClock>(
-          fut, 1, -1, MockClock::duration::max())));
+      MemoryIdler::futexWaitUntil(
+          fut, 1, forever, -1, std::chrono::seconds(-7)));
 }