/*
- * Copyright 2014 Facebook, Inc.
+ * Copyright 2013-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.
* limitations under the License.
*/
-#include "folly/detail/Futex.h"
-#include "folly/test/DeterministicSchedule.h"
+#include <folly/detail/Futex.h>
+#include <folly/test/DeterministicSchedule.h>
#include <chrono>
+#include <functional>
+#include <ratio>
#include <thread>
-#include <gflags/gflags.h>
-#include <gtest/gtest.h>
-#include <common/logging/logging.h>
-#include <time.h>
+#include <glog/logging.h>
+
+#include <folly/Chrono.h>
+#include <folly/portability/GTest.h>
+#include <folly/portability/Time.h>
using namespace folly::detail;
using namespace folly::test;
+using namespace std;
using namespace std::chrono;
+using folly::chrono::coarse_steady_clock;
typedef DeterministicSchedule DSched;
-template <template<typename> class Atom>
+template <template <typename> class Atom>
+void run_basic_thread(
+ Futex<Atom>& f) {
+ EXPECT_EQ(FutexResult::AWOKEN, f.futexWait(0));
+}
+
+template <template <typename> class Atom>
void run_basic_tests() {
Futex<Atom> f(0);
- EXPECT_FALSE(f.futexWait(1));
+ EXPECT_EQ(FutexResult::VALUE_CHANGED, f.futexWait(1));
EXPECT_EQ(f.futexWake(), 0);
- auto thr = DSched::thread([&]{
- EXPECT_TRUE(f.futexWait(0));
- });
+ auto thr = DSched::thread(std::bind(run_basic_thread<Atom>, std::ref(f)));
while (f.futexWake() != 1) {
std::this_thread::yield();
DSched::join(thr);
}
-template<template<typename> class Atom>
-void run_wait_until_tests();
+template <template <typename> class Atom, typename Clock, typename Duration>
+void liveClockWaitUntilTests() {
+ Futex<Atom> f(0);
-template <typename Clock>
-void stdAtomicWaitUntilTests() {
- Futex<std::atomic> f(0);
-
- auto thrA = DSched::thread([&]{
- while (true) {
- typename Clock::time_point nowPlus2s = Clock::now() + seconds(2);
- auto res = f.futexWaitUntil(0, nowPlus2s);
- EXPECT_TRUE(res == FutexResult::TIMEDOUT || res == FutexResult::AWOKEN);
- if (res == FutexResult::AWOKEN) {
- break;
+ for (int stress = 0; stress < 1000; ++stress) {
+ auto fp = &f; // workaround for t5336595
+ auto thrA = DSched::thread([fp,stress]{
+ while (true) {
+ const auto deadline = time_point_cast<Duration>(
+ Clock::now() + microseconds(1 << (stress % 20)));
+ const auto res = fp->futexWaitUntil(0, deadline);
+ EXPECT_TRUE(res == FutexResult::TIMEDOUT || res == FutexResult::AWOKEN);
+ if (res == FutexResult::AWOKEN) {
+ break;
+ }
}
+ });
+
+ while (f.futexWake() != 1) {
+ std::this_thread::yield();
}
- });
- while (f.futexWake() != 1) {
- std::this_thread::yield();
+ DSched::join(thrA);
}
- DSched::join(thrA);
+ {
+ const auto start = Clock::now();
+ const auto deadline = time_point_cast<Duration>(start + milliseconds(100));
+ EXPECT_EQ(f.futexWaitUntil(0, deadline), FutexResult::TIMEDOUT);
+ LOG(INFO) << "Futex wait timed out after waiting for "
+ << duration_cast<milliseconds>(Clock::now() - start).count()
+ << "ms using clock with " << Duration::period::den
+ << " precision, should be ~100ms";
+ }
- auto start = Clock::now();
- EXPECT_EQ(f.futexWaitUntil(0, start + milliseconds(100)),
- FutexResult::TIMEDOUT);
- LOG(INFO) << "Futex wait timed out after waiting for "
- << duration_cast<milliseconds>(Clock::now() - start).count()
- << "ms";
+ {
+ const auto start = Clock::now();
+ const auto deadline = time_point_cast<Duration>(
+ start - 2 * start.time_since_epoch());
+ EXPECT_EQ(f.futexWaitUntil(0, deadline), FutexResult::TIMEDOUT);
+ LOG(INFO) << "Futex wait with invalid deadline timed out after waiting for "
+ << duration_cast<milliseconds>(Clock::now() - start).count()
+ << "ms using clock with " << Duration::period::den
+ << " precision, should be ~0ms";
+ }
}
template <typename Clock>
// Futex wait must eventually fail with either FutexResult::TIMEDOUT or
// FutexResult::INTERRUPTED
- auto res = f.futexWaitUntil(0, Clock::now() + milliseconds(100));
+ const auto res = f.futexWaitUntil(0, Clock::now() + milliseconds(100));
EXPECT_TRUE(res == FutexResult::TIMEDOUT || res == FutexResult::INTERRUPTED);
}
-template <>
-void run_wait_until_tests<std::atomic>() {
- stdAtomicWaitUntilTests<system_clock>();
- stdAtomicWaitUntilTests<steady_clock>();
+template <template <typename> class Atom>
+void run_wait_until_tests() {
+ liveClockWaitUntilTests<Atom, system_clock, system_clock::duration>();
+ liveClockWaitUntilTests<Atom, steady_clock, steady_clock::duration>();
+ liveClockWaitUntilTests<Atom, steady_clock, coarse_steady_clock::duration>();
+
+ typedef duration<int64_t, std::ratio<1, 10000000>> decimicroseconds;
+ liveClockWaitUntilTests<Atom, system_clock, decimicroseconds>();
}
template <>
void run_wait_until_tests<DeterministicAtomic>() {
deterministicAtomicWaitUntilTests<system_clock>();
deterministicAtomicWaitUntilTests<steady_clock>();
+ deterministicAtomicWaitUntilTests<coarse_steady_clock>();
}
uint64_t diff(uint64_t a, uint64_t b) {
struct timespec ts;
const int maxIters = 1000;
int iter = 0;
- uint64_t delta = 10000000 /* 10 ms */;
+ const uint64_t delta = 10000000 /* 10 ms */;
/** The following loop is only to make the test more robust in the presence of
* clock adjustments that can occur. We just run the loop maxIter times and
* for the time_points */
EXPECT_TRUE(steady_clock::is_steady);
- uint64_t A = duration_cast<nanoseconds>(steady_clock::now()
- .time_since_epoch()).count();
+ const uint64_t A = duration_cast<nanoseconds>(steady_clock::now()
+ .time_since_epoch()).count();
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
- uint64_t B = ts.tv_sec * 1000000000ULL + ts.tv_nsec;
+ const uint64_t B = ts.tv_sec * 1000000000ULL + ts.tv_nsec;
- uint64_t C = duration_cast<nanoseconds>(steady_clock::now()
- .time_since_epoch()).count();
+ const uint64_t C = duration_cast<nanoseconds>(steady_clock::now()
+ .time_since_epoch()).count();
EXPECT_TRUE(A <= B && B <= C);
}
+template <template <typename> class Atom>
+void run_wake_blocked_test() {
+ for (auto delay = std::chrono::milliseconds(1);; delay *= 2) {
+ bool success = false;
+ Futex<Atom> f(0);
+ auto thr = DSched::thread(
+ [&] { success = FutexResult::AWOKEN == f.futexWait(0); });
+ /* sleep override */ std::this_thread::sleep_for(delay);
+ f.store(1);
+ f.futexWake(1);
+ DSched::join(thr);
+ LOG(INFO) << "delay=" << delay.count() << "_ms, success=" << success;
+ if (success) {
+ break;
+ }
+ }
+}
+
TEST(Futex, clock_source) {
run_system_clock_test();
run_wait_until_tests<std::atomic>();
}
+TEST(Futex, basic_emulated) {
+ run_basic_tests<EmulatedFutexAtomic>();
+ run_wait_until_tests<EmulatedFutexAtomic>();
+}
+
TEST(Futex, basic_deterministic) {
DSched sched(DSched::uniform(0));
run_basic_tests<DeterministicAtomic>();
run_wait_until_tests<DeterministicAtomic>();
}
-int main(int argc, char ** argv) {
- testing::InitGoogleTest(&argc, argv);
- google::ParseCommandLineFlags(&argc, &argv, true);
- return RUN_ALL_TESTS();
+TEST(Futex, wake_blocked_live) {
+ run_wake_blocked_test<std::atomic>();
}
+TEST(Futex, wake_blocked_emulated) {
+ run_wake_blocked_test<EmulatedFutexAtomic>();
+}