Commit d43e710c authored by Phil Willoughby's avatar Phil Willoughby Committed by Facebook Github Bot 6

Convert a polling loop to a futex wait

Summary:Add a new method to MPMCQueue:
```
template <class Clock, typename... Args>
  bool tryWriteUntil(const std::chrono::time_point<Clock>& when,
                     Args&&... args) noexcept
```
This allows you to write producers which terminate reliably in the absence of consumers.

Returns `true` if `args` was enqueued, `false` otherwise.

`Clock` must be one of the types supported by the underlying call to `folly::detail::Futex::futexWaitUntil`; at time of writing these are `std::chrono::steady_clock` and `std::chrono::system_clock`.

Reviewed By: nbronson

Differential Revision: D2895574

fb-gh-sync-id: bdfabcd043191c149f1271e30ffc28476cc8a36e
shipit-source-id: bdfabcd043191c149f1271e30ffc28476cc8a36e
parent f509d73d
...@@ -284,6 +284,21 @@ class MPMCQueue : boost::noncopyable { ...@@ -284,6 +284,21 @@ class MPMCQueue : boost::noncopyable {
} }
} }
template <class Clock, typename... Args>
bool tryWriteUntil(const std::chrono::time_point<Clock>& when,
Args&&... args) noexcept {
uint64_t ticket;
if (tryObtainPromisedPushTicketUntil(ticket, when)) {
// we have pre-validated that the ticket won't block, or rather that
// it won't block longer than it takes another thread to dequeue an
// element from the slot it identifies.
enqueueWithTicket(ticket, std::forward<Args>(args)...);
return true;
} else {
return false;
}
}
/// If the queue is not full, enqueues and returns true, otherwise /// If the queue is not full, enqueues and returns true, otherwise
/// returns false. Unlike write this method can be blocked by another /// returns false. Unlike write this method can be blocked by another
/// thread, specifically a read that has linearized (been assigned /// thread, specifically a read that has linearized (been assigned
...@@ -471,6 +486,28 @@ class MPMCQueue : boost::noncopyable { ...@@ -471,6 +486,28 @@ class MPMCQueue : boost::noncopyable {
} }
} }
/// Tries until when to obtain a push ticket for which
/// SingleElementQueue::enqueue won't block. Returns true on success, false
/// on failure.
/// ticket is filled on success AND failure.
template <class Clock>
bool tryObtainPromisedPushTicketUntil(
uint64_t& ticket, const std::chrono::time_point<Clock>& when) noexcept {
bool deadlineReached = false;
while (!deadlineReached) {
if (tryObtainPromisedPushTicket(ticket)) {
return true;
}
// ticket is a blocking ticket until the preceding ticket has been
// processed: wait until this ticket's turn arrives. We have not reserved
// this ticket so we will have to re-attempt to get a non-blocking ticket
// if we wake up before we time-out.
deadlineReached = !slots_[idx(ticket)].tryWaitForEnqueueTurnUntil(
turn(ticket), pushSpinCutoff_, (ticket % kAdaptationFreq) == 0, when);
}
return false;
}
/// Tries to obtain a push ticket which can be satisfied if all /// Tries to obtain a push ticket which can be satisfied if all
/// in-progress pops complete. This function does not block, but /// in-progress pops complete. This function does not block, but
/// blocking may be required when using the returned ticket if some /// blocking may be required when using the returned ticket if some
...@@ -482,6 +519,7 @@ class MPMCQueue : boost::noncopyable { ...@@ -482,6 +519,7 @@ class MPMCQueue : boost::noncopyable {
auto numPops = popTicket_.load(std::memory_order_acquire); // B auto numPops = popTicket_.load(std::memory_order_acquire); // B
// n will be negative if pops are pending // n will be negative if pops are pending
int64_t n = numPushes - numPops; int64_t n = numPushes - numPops;
rv = numPushes;
if (n >= static_cast<ssize_t>(capacity_)) { if (n >= static_cast<ssize_t>(capacity_)) {
// Full, linearize at B. We don't need to recheck the read we // Full, linearize at B. We don't need to recheck the read we
// performed at A, because if numPushes was stale at B then the // performed at A, because if numPushes was stale at B then the
...@@ -489,7 +527,6 @@ class MPMCQueue : boost::noncopyable { ...@@ -489,7 +527,6 @@ class MPMCQueue : boost::noncopyable {
return false; return false;
} }
if (pushTicket_.compare_exchange_strong(numPushes, numPushes + 1)) { if (pushTicket_.compare_exchange_strong(numPushes, numPushes + 1)) {
rv = numPushes;
return true; return true;
} }
} }
...@@ -597,7 +634,7 @@ struct SingleElementQueue { ...@@ -597,7 +634,7 @@ struct SingleElementQueue {
template <typename = typename std::enable_if< template <typename = typename std::enable_if<
(folly::IsRelocatable<T>::value && (folly::IsRelocatable<T>::value &&
boost::has_nothrow_constructor<T>::value) || boost::has_nothrow_constructor<T>::value) ||
std::is_nothrow_constructible<T,T&&>::value>::type> std::is_nothrow_constructible<T, T&&>::value>::type>
void enqueue(const uint32_t turn, void enqueue(const uint32_t turn,
Atom<uint32_t>& spinCutoff, Atom<uint32_t>& spinCutoff,
const bool updateSpinCutoff, const bool updateSpinCutoff,
...@@ -611,6 +648,20 @@ struct SingleElementQueue { ...@@ -611,6 +648,20 @@ struct SingleElementQueue {
ImplByMove, ImplByRelocation>::type()); ImplByMove, ImplByRelocation>::type());
} }
/// Waits until either:
/// 1: the dequeue turn preceding the given enqueue turn has arrived
/// 2: the given deadline has arrived
/// Case 1 returns true, case 2 returns false.
template <class Clock>
bool tryWaitForEnqueueTurnUntil(
const uint32_t turn,
Atom<uint32_t>& spinCutoff,
const bool updateSpinCutoff,
const std::chrono::time_point<Clock>& when) noexcept {
return sequencer_.tryWaitForTurn(
turn * 2, spinCutoff, updateSpinCutoff, &when);
}
bool mayEnqueue(const uint32_t turn) const noexcept { bool mayEnqueue(const uint32_t turn) const noexcept {
return sequencer_.isTurn(turn * 2); return sequencer_.isTurn(turn * 2);
} }
......
...@@ -85,7 +85,7 @@ struct TurnSequencer { ...@@ -85,7 +85,7 @@ struct TurnSequencer {
Atom<uint32_t>& spinCutoff, Atom<uint32_t>& spinCutoff,
const bool updateSpinCutoff) noexcept { const bool updateSpinCutoff) noexcept {
bool success = tryWaitForTurn(turn, spinCutoff, updateSpinCutoff); bool success = tryWaitForTurn(turn, spinCutoff, updateSpinCutoff);
(void) success; (void)success;
assert(success); assert(success);
} }
...@@ -99,9 +99,15 @@ struct TurnSequencer { ...@@ -99,9 +99,15 @@ struct TurnSequencer {
/// before blocking and will adjust spinCutoff based on the results, /// before blocking and will adjust spinCutoff based on the results,
/// otherwise it will spin for at most spinCutoff spins. /// otherwise it will spin for at most spinCutoff spins.
/// Returns true if the wait succeeded, false if the turn is in the past /// Returns true if the wait succeeded, false if the turn is in the past
/// or the absTime time value is not nullptr and is reached before the turn
/// arrives
template <class Clock = std::chrono::steady_clock,
class Duration = typename Clock::duration>
bool tryWaitForTurn(const uint32_t turn, bool tryWaitForTurn(const uint32_t turn,
Atom<uint32_t>& spinCutoff, Atom<uint32_t>& spinCutoff,
const bool updateSpinCutoff) noexcept { const bool updateSpinCutoff,
const std::chrono::time_point<Clock, Duration>* absTime =
nullptr) noexcept {
uint32_t prevThresh = spinCutoff.load(std::memory_order_relaxed); uint32_t prevThresh = spinCutoff.load(std::memory_order_relaxed);
const uint32_t effectiveSpinCutoff = const uint32_t effectiveSpinCutoff =
updateSpinCutoff || prevThresh == 0 ? kMaxSpins : prevThresh; updateSpinCutoff || prevThresh == 0 ? kMaxSpins : prevThresh;
...@@ -142,8 +148,16 @@ struct TurnSequencer { ...@@ -142,8 +148,16 @@ struct TurnSequencer {
continue; continue;
} }
} }
if (absTime) {
auto futexResult =
state_.futexWaitUntil(new_state, *absTime, futexChannel(turn));
if (futexResult == FutexResult::TIMEDOUT) {
return false;
}
} else {
state_.futexWait(new_state, futexChannel(turn)); state_.futexWait(new_state, futexChannel(turn));
} }
}
if (updateSpinCutoff || prevThresh == 0) { if (updateSpinCutoff || prevThresh == 0) {
// if we hit kMaxSpins then spinning was pointless, so the right // if we hit kMaxSpins then spinning was pointless, so the right
...@@ -179,8 +193,8 @@ struct TurnSequencer { ...@@ -179,8 +193,8 @@ struct TurnSequencer {
while (true) { while (true) {
assert(state == encode(turn << kTurnShift, decodeMaxWaitersDelta(state))); assert(state == encode(turn << kTurnShift, decodeMaxWaitersDelta(state)));
uint32_t max_waiter_delta = decodeMaxWaitersDelta(state); uint32_t max_waiter_delta = decodeMaxWaitersDelta(state);
uint32_t new_state = encode( uint32_t new_state =
(turn + 1) << kTurnShift, encode((turn + 1) << kTurnShift,
max_waiter_delta == 0 ? 0 : max_waiter_delta - 1); max_waiter_delta == 0 ? 0 : max_waiter_delta - 1);
if (state_.compare_exchange_strong(state, new_state)) { if (state_.compare_exchange_strong(state, new_state)) {
if (max_waiter_delta != 0) { if (max_waiter_delta != 0) {
...@@ -227,9 +241,7 @@ struct TurnSequencer { ...@@ -227,9 +241,7 @@ struct TurnSequencer {
/// Returns the bitmask to pass futexWait or futexWake when communicating /// Returns the bitmask to pass futexWait or futexWake when communicating
/// about the specified turn /// about the specified turn
int futexChannel(uint32_t turn) const noexcept { int futexChannel(uint32_t turn) const noexcept { return 1 << (turn & 31); }
return 1 << (turn & 31);
}
uint32_t decodeCurrentSturn(uint32_t state) const noexcept { uint32_t decodeCurrentSturn(uint32_t state) const noexcept {
return state & ~kWaitersMask; return state & ~kWaitersMask;
...@@ -240,7 +252,7 @@ struct TurnSequencer { ...@@ -240,7 +252,7 @@ struct TurnSequencer {
} }
uint32_t encode(uint32_t currentSturn, uint32_t maxWaiterD) const noexcept { uint32_t encode(uint32_t currentSturn, uint32_t maxWaiterD) const noexcept {
return currentSturn | std::min(uint32_t{ kWaitersMask }, maxWaiterD); return currentSturn | std::min(uint32_t{kWaitersMask}, maxWaiterD);
} }
}; };
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment