blob: d1f4bff3396bd22500229458008b63b7f73d67e5 [file] [log] [blame]
/*
* Copyright 2015 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <folly/detail/Futex.h>
#include <folly/test/DeterministicSchedule.h>
#include <chrono>
#include <functional>
#include <ratio>
#include <thread>
#include <gflags/gflags.h>
#include <glog/logging.h>
#include <gtest/gtest.h>
#include <time.h>
using namespace folly::detail;
using namespace folly::test;
using namespace std;
using namespace std::chrono;
typedef DeterministicSchedule DSched;
template <template<typename> class Atom>
void run_basic_thread(
Futex<Atom>& f) {
EXPECT_TRUE(f.futexWait(0));
}
template <template<typename> class Atom>
void run_basic_tests() {
Futex<Atom> f(0);
EXPECT_FALSE(f.futexWait(1));
EXPECT_EQ(f.futexWake(), 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, typename Clock, typename Duration>
void liveClockWaitUntilTests() {
Futex<Atom> f(0);
/* FYI: 100 iterations in the AARCH64 qemu emulator takes about 6.5 minutes */
for (int stress = 0; stress < 100 ; ++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();
}
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";
}
{
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>
void deterministicAtomicWaitUntilTests() {
Futex<DeterministicAtomic> f(0);
// Futex wait must eventually fail with either FutexResult::TIMEDOUT or
// FutexResult::INTERRUPTED
const auto res = f.futexWaitUntil(0, Clock::now() + milliseconds(100));
EXPECT_TRUE(res == FutexResult::TIMEDOUT || res == FutexResult::INTERRUPTED);
}
template<template<typename> class Atom>
void run_wait_until_tests() {
liveClockWaitUntilTests<Atom, system_clock, system_clock::duration>();
liveClockWaitUntilTests<Atom, steady_clock, 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>();
}
uint64_t diff(uint64_t a, uint64_t b) {
return a > b ? a - b : b - a;
}
void run_system_clock_test() {
/* Test to verify that system_clock uses clock_gettime(CLOCK_REALTIME, ...)
* for the time_points */
struct timespec ts;
const int maxIters = 1000;
int iter = 0;
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
* expect with very high probability that there will be atleast one iteration
* of the test during which clock adjustments > delta have not occurred. */
while (iter < maxIters) {
uint64_t a = duration_cast<nanoseconds>(system_clock::now()
.time_since_epoch()).count();
clock_gettime(CLOCK_REALTIME, &ts);
uint64_t b = ts.tv_sec * 1000000000ULL + ts.tv_nsec;
uint64_t c = duration_cast<nanoseconds>(system_clock::now()
.time_since_epoch()).count();
if (diff(a, b) <= delta &&
diff(b, c) <= delta &&
diff(a, c) <= 2 * delta) {
/* Success! system_clock uses CLOCK_REALTIME for time_points */
break;
}
iter++;
}
EXPECT_TRUE(iter < maxIters);
}
void run_steady_clock_test() {
/* Test to verify that steady_clock uses clock_gettime(CLOCK_MONOTONIC, ...)
* for the time_points */
EXPECT_TRUE(steady_clock::is_steady);
const uint64_t A = duration_cast<nanoseconds>(steady_clock::now()
.time_since_epoch()).count();
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
const uint64_t B = ts.tv_sec * 1000000000ULL + ts.tv_nsec;
const uint64_t C = duration_cast<nanoseconds>(steady_clock::now()
.time_since_epoch()).count();
EXPECT_TRUE(A <= B && B <= C);
}
TEST(Futex, clock_source) {
run_system_clock_test();
/* On some systems steady_clock is just an alias for system_clock. So,
* we must skip run_steady_clock_test if the two clocks are the same. */
if (!std::is_same<system_clock,steady_clock>::value) {
run_steady_clock_test();
}
}
TEST(Futex, basic_live) {
run_basic_tests<std::atomic>();
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);
gflags::ParseCommandLineFlags(&argc, &argv, true);
return RUN_ALL_TESTS();
}