/*
- * Copyright 2017 Facebook, Inc.
+ * Copyright 2014-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.
/// 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
+/// The non-blocking version (MayBlock == false) provides more speed
/// by using only load acquire and store release operations in the
-/// critical path, at the cost of disallowing blocking and timing out.
+/// critical path, at the cost of disallowing blocking.
///
/// The current posix semaphore sem_t isn't too bad, but this provides
/// more a bit more speed, inlining, smaller size, a guarantee that
/// DeterministicSchedule. By having a much more restrictive
/// lifecycle we can also add a bunch of assertions that can help to
/// catch race conditions ahead of time.
-template <
- template <typename> class Atom = std::atomic,
- bool Blocking = true> // blocking vs spinning
+template <bool MayBlock = true, template <typename> class Atom = std::atomic>
struct Baton {
- constexpr Baton() : state_(INIT) {}
+ constexpr Baton() noexcept : state_(INIT) {}
Baton(Baton const&) = delete;
Baton& operator=(Baton const&) = delete;
/// It is an error to destroy a Baton on which a thread is currently
/// wait()ing. In practice this means that the waiter usually takes
/// responsibility for destroying the Baton.
- ~Baton() {
+ ~Baton() noexcept {
// The docblock for this function says that it can't be called when
// there is a concurrent waiter. We assume a strong version of this
// requirement in which the caller must _know_ that this is true, they
assert(state_.load(std::memory_order_relaxed) != WAITING);
}
+ FOLLY_ALWAYS_INLINE bool ready() const noexcept {
+ auto s = state_.load(std::memory_order_acquire);
+ assert(s == INIT || s == EARLY_DELIVERY);
+ return LIKELY(s == EARLY_DELIVERY);
+ }
+
/// Equivalent to destroying the Baton and creating a new one. It is
/// a bug to call this while there is a waiting thread, so in practice
/// the waiter will be the one that resets the baton.
- void reset() {
+ void reset() noexcept {
// See ~Baton for a discussion about why relaxed is okay here
assert(state_.load(std::memory_order_relaxed) != WAITING);
/// lifetime starts at construction or reset() and ends at
/// destruction or reset()) there can be at most one call to post(),
/// in the single poster version. Any thread may call post().
- void post() {
- if (!Blocking) {
- /// Non-blocking version
+ void post() noexcept {
+ if (!MayBlock) {
+ /// Spin-only version
///
assert([&] {
auto state = state_.load(std::memory_order_relaxed);
return;
}
- /// Blocking versions
+ /// May-block versions
///
uint32_t before = state_.load(std::memory_order_acquire);
assert(before == INIT || before == WAITING || before == TIMED_OUT);
if (before == INIT &&
- state_.compare_exchange_strong(before, EARLY_DELIVERY)) {
+ state_.compare_exchange_strong(
+ before,
+ EARLY_DELIVERY,
+ std::memory_order_release,
+ std::memory_order_relaxed)) {
return;
}
/// could be relaxed somewhat without any perf or size regressions,
/// but by making this condition very restrictive we can provide better
/// checking in debug builds.
- FOLLY_ALWAYS_INLINE void wait() {
+ FOLLY_ALWAYS_INLINE void wait() noexcept {
if (try_wait()) {
return;
}
/// call wait, try_wait or timed_wait on the same baton without resetting
///
/// @return true if baton has been posted, false othewise
- FOLLY_ALWAYS_INLINE bool try_wait() const {
- auto s = state_.load(std::memory_order_acquire);
- assert(s == INIT || s == EARLY_DELIVERY);
- return LIKELY(s == EARLY_DELIVERY);
+ FOLLY_ALWAYS_INLINE bool try_wait() const noexcept {
+ return ready();
}
/// Similar to wait, but with a timeout. The thread is unblocked if the
/// false otherwise
template <typename Rep, typename Period>
FOLLY_ALWAYS_INLINE bool try_wait_for(
- const std::chrono::duration<Rep, Period>& timeout) {
- static_assert(
- Blocking, "Non-blocking Baton does not support try_wait_for.");
-
+ const std::chrono::duration<Rep, Period>& timeout) noexcept {
if (try_wait()) {
return true;
}
/// false otherwise
template <typename Clock, typename Duration>
FOLLY_ALWAYS_INLINE bool try_wait_until(
- const std::chrono::time_point<Clock, Duration>& deadline) {
- static_assert(
- Blocking, "Non-blocking Baton does not support try_wait_until.");
-
+ const std::chrono::time_point<Clock, Duration>& deadline) noexcept {
if (try_wait()) {
return true;
}
/// Alias to try_wait_for. Deprecated.
template <typename Rep, typename Period>
FOLLY_ALWAYS_INLINE bool timed_wait(
- const std::chrono::duration<Rep, Period>& timeout) {
+ const std::chrono::duration<Rep, Period>& timeout) noexcept {
return try_wait_for(timeout);
}
/// Alias to try_wait_until. Deprecated.
template <typename Clock, typename Duration>
FOLLY_ALWAYS_INLINE bool timed_wait(
- const std::chrono::time_point<Clock, Duration>& deadline) {
+ const std::chrono::time_point<Clock, Duration>& deadline) noexcept {
return try_wait_until(deadline);
}
// @return true if we received an early delivery during the wait,
// false otherwise. If the function returns true then
// state_ is guaranteed to be EARLY_DELIVERY
- bool spinWaitForEarlyDelivery() {
- static_assert(
- PreBlockAttempts > 0,
- "isn't this assert clearer than an uninitialized variable warning?");
+ template <typename Clock, typename Duration>
+ bool spinWaitForEarlyDelivery(
+ const std::chrono::time_point<Clock, Duration>& deadline) noexcept {
for (int i = 0; i < PreBlockAttempts; ++i) {
if (try_wait()) {
return true;
}
-
+ if (Clock::now() >= deadline) {
+ return false;
+ }
// The pause instruction is the polite way to spin, but it doesn't
// actually affect correctness to omit it if we don't have it.
// Pausing donates the full capabilities of the current core to
return false;
}
- FOLLY_NOINLINE void waitSlow() {
- if (spinWaitForEarlyDelivery()) {
+ FOLLY_NOINLINE void waitSlow() noexcept {
+ auto const deadline = std::chrono::steady_clock::time_point::max();
+ if (spinWaitForEarlyDelivery(deadline)) {
assert(state_.load(std::memory_order_acquire) == EARLY_DELIVERY);
return;
}
- if (!Blocking) {
+ if (!MayBlock) {
while (!try_wait()) {
std::this_thread::yield();
}
template <typename Clock, typename Duration>
FOLLY_NOINLINE bool tryWaitUntilSlow(
- const std::chrono::time_point<Clock, Duration>& deadline) {
- if (spinWaitForEarlyDelivery()) {
+ const std::chrono::time_point<Clock, Duration>& deadline) noexcept {
+ if (spinWaitForEarlyDelivery(deadline)) {
assert(state_.load(std::memory_order_acquire) == EARLY_DELIVERY);
return true;
}
+ if (!MayBlock) {
+ while (true) {
+ if (try_wait()) {
+ return true;
+ }
+ if (Clock::now() >= deadline) {
+ return false;
+ }
+ std::this_thread::yield();
+ }
+ }
+
// guess we have to block :(
uint32_t expected = INIT;
if (!state_.compare_exchange_strong(expected, WAITING)) {