namespace folly {
-/// A Baton allows a thread to block once and be awoken. The single
-/// poster version (with SinglePoster == true) captures a single
-/// handoff, and during its lifecycle (from construction/reset to
-/// destruction/reset) a baton must either be post()ed and wait()ed
+/// A Baton allows a thread to block once and be awoken. Captures a
+/// single handoff, and during its lifecycle (from construction/reset
+/// to destruction/reset) a baton must either be post()ed and wait()ed
/// exactly once each, or not at all.
///
-/// The multi-poster version (SinglePoster == false) allows multiple
-/// concurrent handoff attempts, the first of which completes the
-/// handoff and the rest if any are idempotent.
-///
/// Baton includes no internal padding, and is only 4 bytes in size.
/// Any alignment or padding to avoid false sharing is up to the user.
///
-/// This is basically a stripped-down semaphore that supports (only a
-/// single call to sem_post, when SinglePoster == true) and a single
-/// call to sem_wait.
+/// This is basically a stripped-down semaphore that supports only a
+/// single call to sem_post and a single call to sem_wait.
///
/// The non-blocking version (Blocking == false) provides more speed
/// by using only load acquire and store release operations in the
/// catch race conditions ahead of time.
template <
template <typename> class Atom = std::atomic,
- bool SinglePoster = true, // single vs multiple posters
bool Blocking = true> // blocking vs spinning
struct Baton {
constexpr Baton() : state_(INIT) {}
/// Blocking versions
///
- if (SinglePoster) {
- /// Single poster version
- ///
- uint32_t before = state_.load(std::memory_order_acquire);
+ uint32_t before = state_.load(std::memory_order_acquire);
- assert(before == INIT || before == WAITING || before == TIMED_OUT);
+ assert(before == INIT || before == WAITING || before == TIMED_OUT);
- if (before == INIT &&
- state_.compare_exchange_strong(before, EARLY_DELIVERY)) {
- return;
- }
-
- assert(before == WAITING || before == TIMED_OUT);
+ if (before == INIT &&
+ state_.compare_exchange_strong(before, EARLY_DELIVERY)) {
+ return;
+ }
- if (before == TIMED_OUT) {
- return;
- }
+ assert(before == WAITING || before == TIMED_OUT);
- assert(before == WAITING);
- state_.store(LATE_DELIVERY, std::memory_order_release);
- state_.futexWake(1);
- } else {
- /// Multi-poster version
- ///
- while (true) {
- uint32_t before = state_.load(std::memory_order_acquire);
-
- if (before == INIT &&
- state_.compare_exchange_strong(before, EARLY_DELIVERY)) {
- return;
- }
-
- if (before == TIMED_OUT) {
- return;
- }
-
- if (before == EARLY_DELIVERY || before == LATE_DELIVERY) {
- // The reason for not simply returning (without the following
- // atomic operation) is to avoid the following case:
- //
- // T1: T2: T3:
- // local1.post(); local2.post(); global.wait();
- // global.post(); global.post(); local1.try_wait() == true;
- // local2.try_wait() == false;
- //
- if (state_.fetch_add(0) != before) {
- continue;
- }
- return;
- }
-
- assert(before == WAITING);
- if (!state_.compare_exchange_weak(before, LATE_DELIVERY)) {
- continue;
- }
- state_.futexWake(1);
- return;
- }
+ if (before == TIMED_OUT) {
+ return;
}
+
+ assert(before == WAITING);
+ state_.store(LATE_DELIVERY, std::memory_order_release);
+ state_.futexWake(1);
}
/// Waits until post() has been called in the current Baton lifetime.
#include <thread>
#include <folly/Benchmark.h>
-#include <folly/portability/GFlags.h>
-#include <folly/portability/GTest.h>
#include <folly/portability/Semaphore.h>
#include <folly/synchronization/test/BatonTestHelpers.h>
#include <folly/test/DeterministicSchedule.h>
typedef DeterministicSchedule DSched;
-BENCHMARK(baton_pingpong_single_poster_blocking, iters) {
- run_pingpong_test<std::atomic, true, true>(iters);
+BENCHMARK(baton_pingpong_blocking, iters) {
+ run_pingpong_test<std::atomic, true>(iters);
}
-BENCHMARK(baton_pingpong_multi_poster_blocking, iters) {
- run_pingpong_test<std::atomic, false, true>(iters);
-}
-
-BENCHMARK(baton_pingpong_single_poster_nonblocking, iters) {
- run_pingpong_test<std::atomic, true, false>(iters);
-}
-
-BENCHMARK(baton_pingpong_multi_poster_nonblocking, iters) {
- run_pingpong_test<std::atomic, false, false>(iters);
+BENCHMARK(baton_pingpong_nonblocking, iters) {
+ run_pingpong_test<std::atomic, false>(iters);
}
BENCHMARK_DRAW_LINE()
-BENCHMARK(baton_pingpong_emulated_futex_single_poster_blocking, iters) {
- run_pingpong_test<EmulatedFutexAtomic, true, true>(iters);
-}
-
-BENCHMARK(baton_pingpong_emulated_futex_multi_poster_blocking, iters) {
- run_pingpong_test<EmulatedFutexAtomic, false, true>(iters);
-}
-
-BENCHMARK(baton_pingpong_emulated_futex_single_poster_nonblocking, iters) {
- run_pingpong_test<EmulatedFutexAtomic, true, false>(iters);
+BENCHMARK(baton_pingpong_emulated_futex_blocking, iters) {
+ run_pingpong_test<EmulatedFutexAtomic, true>(iters);
}
-BENCHMARK(baton_pingpong_emulated_futex_multi_poster_nonblocking, iters) {
- run_pingpong_test<EmulatedFutexAtomic, false, false>(iters);
+BENCHMARK(baton_pingpong_emulated_futex_nonblocking, iters) {
+ run_pingpong_test<EmulatedFutexAtomic, false>(iters);
}
BENCHMARK_DRAW_LINE()
// to the required futex calls for the blocking case
int main(int argc, char** argv) {
- testing::InitGoogleTest(&argc, argv);
gflags::ParseCommandLineFlags(&argc, &argv, true);
-
- auto rv = RUN_ALL_TESTS();
- if (!rv && FLAGS_benchmark) {
- folly::runBenchmarks();
- }
- return rv;
+ folly::runBenchmarks();
+ return 0;
}
/// Basic test
-TEST(Baton, basic_single_poster_blocking) {
- run_basic_test<std::atomic, true, true>();
- run_basic_test<EmulatedFutexAtomic, true, true>();
- run_basic_test<DeterministicAtomic, true, true>();
+TEST(Baton, basic_blocking) {
+ run_basic_test<std::atomic, true>();
+ run_basic_test<EmulatedFutexAtomic, true>();
+ run_basic_test<DeterministicAtomic, true>();
}
-TEST(Baton, basic_single_poster_nonblocking) {
- run_basic_test<std::atomic, true, false>();
- run_basic_test<EmulatedFutexAtomic, true, false>();
- run_basic_test<DeterministicAtomic, true, false>();
-}
-
-TEST(Baton, basic_multi_poster_blocking) {
- run_basic_test<std::atomic, false, true>();
-}
-
-TEST(Baton, basic_multi_poster_nonblocking) {
- run_basic_test<std::atomic, false, false>();
+TEST(Baton, basic_nonblocking) {
+ run_basic_test<std::atomic, false>();
+ run_basic_test<EmulatedFutexAtomic, false>();
+ run_basic_test<DeterministicAtomic, false>();
}
/// Ping pong tests
-TEST(Baton, pingpong_single_poster_blocking) {
- DSched sched(DSched::uniform(0));
-
- run_pingpong_test<DeterministicAtomic, true, true>(1000);
-}
-
-TEST(Baton, pingpong_single_poster_nonblocking) {
+TEST(Baton, pingpong_blocking) {
DSched sched(DSched::uniform(0));
- run_pingpong_test<DeterministicAtomic, true, false>(1000);
+ run_pingpong_test<DeterministicAtomic, true>(1000);
}
-TEST(Baton, pingpong_multi_poster_blocking) {
+TEST(Baton, pingpong_nonblocking) {
DSched sched(DSched::uniform(0));
- run_pingpong_test<DeterministicAtomic, false, true>(1000);
-}
-
-TEST(Baton, pingpong_multi_poster_nonblocking) {
- DSched sched(DSched::uniform(0));
-
- run_pingpong_test<DeterministicAtomic, false, false>(1000);
+ run_pingpong_test<DeterministicAtomic, false>(1000);
}
/// Timed wait tests - Nonblocking Baton does not support timed_wait()
// Timed wait basic system clock tests
-TEST(Baton, timed_wait_basic_system_clock_single_poster) {
- run_basic_timed_wait_tests<std::atomic, std::chrono::system_clock, true>();
- run_basic_timed_wait_tests<
- EmulatedFutexAtomic,
- std::chrono::system_clock,
- true>();
- run_basic_timed_wait_tests<
- DeterministicAtomic,
- std::chrono::system_clock,
- true>();
-}
-
-TEST(Baton, timed_wait_basic_system_clock_multi_poster) {
- run_basic_timed_wait_tests<std::atomic, std::chrono::system_clock, false>();
- run_basic_timed_wait_tests<
- EmulatedFutexAtomic,
- std::chrono::system_clock,
- false>();
- run_basic_timed_wait_tests<
- DeterministicAtomic,
- std::chrono::system_clock,
- false>();
+TEST(Baton, timed_wait_basic_system_clock) {
+ run_basic_timed_wait_tests<std::atomic, std::chrono::system_clock>();
+ run_basic_timed_wait_tests<EmulatedFutexAtomic, std::chrono::system_clock>();
+ run_basic_timed_wait_tests<DeterministicAtomic, std::chrono::system_clock>();
}
// Timed wait timeout system clock tests
-TEST(Baton, timed_wait_timeout_system_clock_single_poster) {
- run_timed_wait_tmo_tests<std::atomic, std::chrono::system_clock, true>();
- run_timed_wait_tmo_tests<
- EmulatedFutexAtomic,
- std::chrono::system_clock,
- true>();
- run_timed_wait_tmo_tests<
- DeterministicAtomic,
- std::chrono::system_clock,
- true>();
-}
-
-TEST(Baton, timed_wait_timeout_system_clock_multi_poster) {
- run_timed_wait_tmo_tests<std::atomic, std::chrono::system_clock, false>();
- run_timed_wait_tmo_tests<
- EmulatedFutexAtomic,
- std::chrono::system_clock,
- false>();
- run_timed_wait_tmo_tests<
- DeterministicAtomic,
- std::chrono::system_clock,
- false>();
+TEST(Baton, timed_wait_timeout_system_clock) {
+ run_timed_wait_tmo_tests<std::atomic, std::chrono::system_clock>();
+ run_timed_wait_tmo_tests<EmulatedFutexAtomic, std::chrono::system_clock>();
+ run_timed_wait_tmo_tests<DeterministicAtomic, std::chrono::system_clock>();
}
// Timed wait regular system clock tests
-TEST(Baton, timed_wait_system_clock_single_poster) {
- run_timed_wait_regular_test<std::atomic, std::chrono::system_clock, true>();
- run_timed_wait_regular_test<
- EmulatedFutexAtomic,
- std::chrono::system_clock,
- true>();
- run_timed_wait_regular_test<
- DeterministicAtomic,
- std::chrono::system_clock,
- true>();
-}
-
-TEST(Baton, timed_wait_system_clock_multi_poster) {
- run_timed_wait_regular_test<std::atomic, std::chrono::system_clock, false>();
- run_timed_wait_regular_test<
- EmulatedFutexAtomic,
- std::chrono::system_clock,
- false>();
- run_timed_wait_regular_test<
- DeterministicAtomic,
- std::chrono::system_clock,
- false>();
+TEST(Baton, timed_wait_system_clock) {
+ run_timed_wait_regular_test<std::atomic, std::chrono::system_clock>();
+ run_timed_wait_regular_test<EmulatedFutexAtomic, std::chrono::system_clock>();
+ run_timed_wait_regular_test<DeterministicAtomic, std::chrono::system_clock>();
}
// Timed wait basic steady clock tests
-TEST(Baton, timed_wait_basic_steady_clock_single_poster) {
- run_basic_timed_wait_tests<std::atomic, std::chrono::steady_clock, true>();
- run_basic_timed_wait_tests<
- EmulatedFutexAtomic,
- std::chrono::steady_clock,
- true>();
- run_basic_timed_wait_tests<
- DeterministicAtomic,
- std::chrono::steady_clock,
- true>();
-}
-
-TEST(Baton, timed_wait_basic_steady_clock_multi_poster) {
- run_basic_timed_wait_tests<std::atomic, std::chrono::steady_clock, false>();
- run_basic_timed_wait_tests<
- EmulatedFutexAtomic,
- std::chrono::steady_clock,
- false>();
- run_basic_timed_wait_tests<
- DeterministicAtomic,
- std::chrono::steady_clock,
- false>();
+TEST(Baton, timed_wait_basic_steady_clock) {
+ run_basic_timed_wait_tests<std::atomic, std::chrono::steady_clock>();
+ run_basic_timed_wait_tests<EmulatedFutexAtomic, std::chrono::steady_clock>();
+ run_basic_timed_wait_tests<DeterministicAtomic, std::chrono::steady_clock>();
}
// Timed wait timeout steady clock tests
-TEST(Baton, timed_wait_timeout_steady_clock_single_poster) {
- run_timed_wait_tmo_tests<std::atomic, std::chrono::steady_clock, true>();
- run_timed_wait_tmo_tests<
- EmulatedFutexAtomic,
- std::chrono::steady_clock,
- true>();
- run_timed_wait_tmo_tests<
- DeterministicAtomic,
- std::chrono::steady_clock,
- true>();
-}
-
-TEST(Baton, timed_wait_timeout_steady_clock_multi_poster) {
- run_timed_wait_tmo_tests<std::atomic, std::chrono::steady_clock, false>();
- run_timed_wait_tmo_tests<
- EmulatedFutexAtomic,
- std::chrono::steady_clock,
- false>();
- run_timed_wait_tmo_tests<
- DeterministicAtomic,
- std::chrono::steady_clock,
- false>();
+TEST(Baton, timed_wait_timeout_steady_clock) {
+ run_timed_wait_tmo_tests<std::atomic, std::chrono::steady_clock>();
+ run_timed_wait_tmo_tests<EmulatedFutexAtomic, std::chrono::steady_clock>();
+ run_timed_wait_tmo_tests<DeterministicAtomic, std::chrono::steady_clock>();
}
// Timed wait regular steady clock tests
-TEST(Baton, timed_wait_steady_clock_single_poster) {
- run_timed_wait_regular_test<std::atomic, std::chrono::steady_clock, true>();
- run_timed_wait_regular_test<
- EmulatedFutexAtomic,
- std::chrono::steady_clock,
- true>();
- run_timed_wait_regular_test<
- DeterministicAtomic,
- std::chrono::steady_clock,
- true>();
-}
-
-TEST(Baton, timed_wait_steady_clock_multi_poster) {
- run_timed_wait_regular_test<std::atomic, std::chrono::steady_clock, false>();
- run_timed_wait_regular_test<
- EmulatedFutexAtomic,
- std::chrono::steady_clock,
- false>();
- run_timed_wait_regular_test<
- DeterministicAtomic,
- std::chrono::steady_clock,
- false>();
+TEST(Baton, timed_wait_steady_clock) {
+ run_timed_wait_regular_test<std::atomic, std::chrono::steady_clock>();
+ run_timed_wait_regular_test<EmulatedFutexAtomic, std::chrono::steady_clock>();
+ run_timed_wait_regular_test<DeterministicAtomic, std::chrono::steady_clock>();
}
/// Try wait tests
-TEST(Baton, try_wait_single_poster_blocking) {
- run_try_wait_tests<std::atomic, true, true>();
- run_try_wait_tests<EmulatedFutexAtomic, true, true>();
- run_try_wait_tests<DeterministicAtomic, true, true>();
-}
-
-TEST(Baton, try_wait_single_poster_nonblocking) {
- run_try_wait_tests<std::atomic, true, false>();
- run_try_wait_tests<EmulatedFutexAtomic, true, false>();
- run_try_wait_tests<DeterministicAtomic, true, false>();
-}
-
-TEST(Baton, try_wait_multi_poster_blocking) {
- run_try_wait_tests<std::atomic, false, true>();
- run_try_wait_tests<EmulatedFutexAtomic, false, true>();
- run_try_wait_tests<DeterministicAtomic, false, true>();
-}
-
-TEST(Baton, try_wait_multi_poster_nonblocking) {
- run_try_wait_tests<std::atomic, false, false>();
- run_try_wait_tests<EmulatedFutexAtomic, false, false>();
- run_try_wait_tests<DeterministicAtomic, false, false>();
-}
-
-/// Multi-producer tests
-
-TEST(Baton, multi_producer_single_poster_blocking) {
- run_try_wait_tests<std::atomic, true, true>();
- run_try_wait_tests<EmulatedFutexAtomic, true, true>();
- run_try_wait_tests<DeterministicAtomic, true, true>();
-}
-
-TEST(Baton, multi_producer_single_poster_nonblocking) {
- run_try_wait_tests<std::atomic, true, false>();
- run_try_wait_tests<EmulatedFutexAtomic, true, false>();
- run_try_wait_tests<DeterministicAtomic, true, false>();
-}
-
-TEST(Baton, multi_producer_multi_poster_blocking) {
- run_try_wait_tests<std::atomic, false, true>();
- run_try_wait_tests<EmulatedFutexAtomic, false, true>();
- run_try_wait_tests<DeterministicAtomic, false, true>();
+TEST(Baton, try_wait_blocking) {
+ run_try_wait_tests<std::atomic, true>();
+ run_try_wait_tests<EmulatedFutexAtomic, true>();
+ run_try_wait_tests<DeterministicAtomic, true>();
}
-TEST(Baton, multi_producer_multi_poster_nonblocking) {
- run_try_wait_tests<std::atomic, false, false>();
- run_try_wait_tests<EmulatedFutexAtomic, false, false>();
- run_try_wait_tests<DeterministicAtomic, false, false>();
+TEST(Baton, try_wait_nonblocking) {
+ run_try_wait_tests<std::atomic, false>();
+ run_try_wait_tests<EmulatedFutexAtomic, false>();
+ run_try_wait_tests<DeterministicAtomic, false>();
}
typedef DeterministicSchedule DSched;
-template <template <typename> class Atom, bool SinglePoster, bool Blocking>
+template <template <typename> class Atom, bool Blocking>
void run_basic_test() {
- Baton<Atom, SinglePoster, Blocking> b;
+ Baton<Atom, Blocking> b;
b.post();
b.wait();
}
-template <template <typename> class Atom, bool SinglePoster, bool Blocking>
+template <template <typename> class Atom, bool Blocking>
void run_pingpong_test(int numRounds) {
- using B = Baton<Atom, SinglePoster, Blocking>;
+ using B = Baton<Atom, Blocking>;
B batons[17];
B& a = batons[0];
B& b = batons[16]; // to get it on a different cache line
DSched::join(thr);
}
-template <template <typename> class Atom, typename Clock, bool SinglePoster>
+template <template <typename> class Atom, typename Clock>
void run_basic_timed_wait_tests() {
- Baton<Atom, SinglePoster> b;
+ Baton<Atom> b;
b.post();
// tests if early delivery works fine
EXPECT_TRUE(b.timed_wait(Clock::now()));
}
-template <template <typename> class Atom, typename Clock, bool SinglePoster>
+template <template <typename> class Atom, typename Clock>
void run_timed_wait_tmo_tests() {
- Baton<Atom, SinglePoster> b;
+ Baton<Atom> b;
auto thr = DSched::thread([&] {
bool rv = b.timed_wait(Clock::now() + std::chrono::milliseconds(1));
DSched::join(thr);
}
-template <template <typename> class Atom, typename Clock, bool SinglePoster>
+template <template <typename> class Atom, typename Clock>
void run_timed_wait_regular_test() {
- Baton<Atom, SinglePoster> b;
+ Baton<Atom> b;
auto thr = DSched::thread([&] {
// To wait forever we'd like to use time_point<Clock>::max, but
DSched::join(thr);
}
-template <template <typename> class Atom, bool SinglePoster, bool Blocking>
+template <template <typename> class Atom, bool Blocking>
void run_try_wait_tests() {
- Baton<Atom, SinglePoster, Blocking> b;
+ Baton<Atom, Blocking> b;
EXPECT_FALSE(b.try_wait());
b.post();
EXPECT_TRUE(b.try_wait());
}
-template <template <typename> class Atom, bool SinglePoster, bool Blocking>
-void run_multi_producer_tests() {
- constexpr int NPROD = 5;
- Baton<Atom, SinglePoster, Blocking> local_ping[NPROD];
- Baton<Atom, SinglePoster, Blocking> local_pong[NPROD];
- Baton<Atom, /* SingleProducer = */ false, Blocking> global;
- Baton<Atom, SinglePoster, Blocking> shutdown;
-
- std::thread prod[NPROD];
- for (int i = 0; i < NPROD; ++i) {
- prod[i] = DSched::thread([&, i] {
- if (!std::is_same<Atom<int>, DeterministicAtomic<int>>::value) {
- // If we are using std::atomic (or EmulatedFutexAtomic) then
- // a variable sleep here will make it more likely that
- // global.post()-s will span more than one global.wait() by
- // the consumer thread and for the latter to block (if the
- // global baton is blocking). For DeterministicAtomic, we just
- // rely on DeterministicSchedule to do the scheduling. The
- // test won't fail if we lose the race, we just don't get
- // coverage.
- for (int j = 0; j < i; ++j) {
- std::this_thread::sleep_for(std::chrono::microseconds(1));
- }
- }
- local_ping[i].post();
- global.post();
- local_pong[i].wait();
- });
- }
-
- auto cons = DSched::thread([&] {
- while (true) {
- global.wait();
- global.reset();
- if (shutdown.try_wait()) {
- return;
- }
- for (int i = 0; i < NPROD; ++i) {
- if (local_ping.try_wait()) {
- local_ping.reset();
- local_pong.post();
- }
- }
- }
- });
-
- for (auto& t : prod) {
- DSched::join(t);
- }
-
- global.post();
- shutdown.post();
- DSched::join(cons);
-}
-
} // namespace test
} // namespace folly