/// 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;
}
};
/// 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() {
} // namespace detail
} // namespace folly
+static auto const forever = MockClock::time_point::max();
+
TEST(MemoryIdler, futexWaitValueChangedEarly) {
StrictMock<Futex<MockAtom>> fut;
auto clock = MockClock::setup();
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) {
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) {
.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) {
.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)));
}