Commit f5fe4005 authored by Chad Austin's avatar Chad Austin Committed by Facebook GitHub Bot

allow LockFreeRingBuffer::Cursor comparison

Summary:
To support "clearing" a ring-buffer without violating TurnSequencer's
invariants, allow storing a Cursor and comparing them in a loop.

Differential Revision: D29345250

fbshipit-source-id: d1d3bbd0b38489690334bdd63a7f40d9f6bad6c6
parent f29212f6
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
#include <type_traits> #include <type_traits>
#include <utility> #include <utility>
#include <boost/operators.hpp>
#include <folly/Portability.h> #include <folly/Portability.h>
#include <folly/Traits.h> #include <folly/Traits.h>
#include <folly/detail/TurnSequencer.h> #include <folly/detail/TurnSequencer.h>
...@@ -76,7 +77,7 @@ class LockFreeRingBuffer { ...@@ -76,7 +77,7 @@ class LockFreeRingBuffer {
public: public:
/// Opaque pointer to a past or future write. /// Opaque pointer to a past or future write.
/// Can be moved relative to its current location but not in absolute terms. /// Can be moved relative to its current location but not in absolute terms.
struct Cursor { struct Cursor : boost::totally_ordered<Cursor> {
explicit Cursor(uint64_t initialTicket) noexcept : ticket(initialTicket) {} explicit Cursor(uint64_t initialTicket) noexcept : ticket(initialTicket) {}
/// Returns true if this cursor now points to a different /// Returns true if this cursor now points to a different
...@@ -99,6 +100,14 @@ class LockFreeRingBuffer { ...@@ -99,6 +100,14 @@ class LockFreeRingBuffer {
return prevTicket != ticket; return prevTicket != ticket;
} }
bool operator==(const Cursor& that) const noexcept {
return ticket == that.ticket;
}
bool operator<(const Cursor& that) const noexcept {
return ticket < that.ticket;
}
protected: // for test visibility reasons protected: // for test visibility reasons
uint64_t ticket; uint64_t ticket;
friend class LockFreeRingBuffer; friend class LockFreeRingBuffer;
...@@ -110,6 +119,8 @@ class LockFreeRingBuffer { ...@@ -110,6 +119,8 @@ class LockFreeRingBuffer {
LockFreeRingBuffer(const LockFreeRingBuffer&) = delete; LockFreeRingBuffer(const LockFreeRingBuffer&) = delete;
LockFreeRingBuffer& operator=(const LockFreeRingBuffer&) = delete; LockFreeRingBuffer& operator=(const LockFreeRingBuffer&) = delete;
uint32_t capacity() const noexcept { return capacity_; }
/// Perform a single write of an object of type T. /// Perform a single write of an object of type T.
/// Writes can block iff a previous writer has not yet completed a write /// Writes can block iff a previous writer has not yet completed a write
/// for the same slot (before the most recent wrap-around). /// for the same slot (before the most recent wrap-around).
...@@ -135,7 +146,7 @@ class LockFreeRingBuffer { ...@@ -135,7 +146,7 @@ class LockFreeRingBuffer {
/// value is false, dest is to be considered partially read and in an /// value is false, dest is to be considered partially read and in an
/// inconsistent state. Readers are advised to discard it. /// inconsistent state. Readers are advised to discard it.
template <typename V> template <typename V>
bool tryRead(V& dest, const Cursor& cursor) noexcept { bool tryRead(V& dest, const Cursor& cursor) const noexcept {
return slots_[idx(cursor.ticket)].tryRead(dest, turn(cursor.ticket)); return slots_[idx(cursor.ticket)].tryRead(dest, turn(cursor.ticket));
} }
...@@ -149,10 +160,10 @@ class LockFreeRingBuffer { ...@@ -149,10 +160,10 @@ class LockFreeRingBuffer {
} }
/// Returns a Cursor pointing to the first write that has not occurred yet. /// Returns a Cursor pointing to the first write that has not occurred yet.
Cursor currentHead() noexcept { return Cursor(ticket_.load()); } Cursor currentHead() const noexcept { return Cursor(ticket_.load()); }
/// Returns a Cursor pointing to the earliest readable write. /// Returns a Cursor pointing to the earliest readable write.
Cursor currentTail() noexcept { Cursor currentTail() const noexcept {
uint64_t ticket = ticket_.load(); uint64_t ticket = ticket_.load();
// can't go back more steps than we've taken // can't go back more steps than we've taken
...@@ -171,8 +182,6 @@ class LockFreeRingBuffer { ...@@ -171,8 +182,6 @@ class LockFreeRingBuffer {
static_cast<void const*>(slots_.get()), sizeof(Slot[capacity_])); static_cast<void const*>(slots_.get()), sizeof(Slot[capacity_]));
} }
~LockFreeRingBuffer() {}
private: private:
using Slot = detail::RingBufferSlot<T, Atom, Storage>; using Slot = detail::RingBufferSlot<T, Atom, Storage>;
...@@ -182,9 +191,9 @@ class LockFreeRingBuffer { ...@@ -182,9 +191,9 @@ class LockFreeRingBuffer {
Atom<uint64_t> ticket_; Atom<uint64_t> ticket_;
uint32_t idx(uint64_t ticket) noexcept { return ticket % capacity_; } uint32_t idx(uint64_t ticket) const noexcept { return ticket % capacity_; }
uint32_t turn(uint64_t ticket) noexcept { uint32_t turn(uint64_t ticket) const noexcept {
return (uint32_t)(ticket / capacity_); return (uint32_t)(ticket / capacity_);
} }
}; // LockFreeRingBuffer }; // LockFreeRingBuffer
...@@ -228,7 +237,7 @@ class RingBufferSlot { ...@@ -228,7 +237,7 @@ class RingBufferSlot {
} }
template <typename V> template <typename V>
bool tryRead(V& dest, uint32_t turn) noexcept { bool tryRead(V& dest, uint32_t turn) const noexcept {
// The write that started at turn 0 ended at turn 2 // The write that started at turn 0 ended at turn 2
if (!sequencer_.isTurn((turn + 1) * 2)) { if (!sequencer_.isTurn((turn + 1) * 2)) {
return false; return false;
...@@ -265,7 +274,7 @@ class RingBufferTrivialStorage { ...@@ -265,7 +274,7 @@ class RingBufferTrivialStorage {
std::atomic_thread_fence(std::memory_order_release); std::atomic_thread_fence(std::memory_order_release);
} }
void load(T& dest) { void load(T& dest) const {
std::atomic_thread_fence(std::memory_order_acquire); std::atomic_thread_fence(std::memory_order_acquire);
std::memcpy(&dest, &data_, sizeof(T)); std::memcpy(&dest, &data_, sizeof(T));
} }
...@@ -283,7 +292,7 @@ class [[deprecated( ...@@ -283,7 +292,7 @@ class [[deprecated(
public: public:
void store(const T& src) { data_ = src; } void store(const T& src) { data_ = src; }
void load(T & dest) { dest = data_; } void load(T & dest) const { dest = data_; }
private: private:
T data_{}; T data_{};
......
...@@ -257,4 +257,20 @@ TEST(LockFreeRingBuffer, contendedReadsAndWrites) { ...@@ -257,4 +257,20 @@ TEST(LockFreeRingBuffer, contendedReadsAndWrites) {
} }
} }
TEST(LockFreeRingBuffer, cursorComparison) {
LockFreeRingBuffer<int> rb{2};
rb.write(5);
EXPECT_TRUE(rb.currentHead() == rb.currentHead());
EXPECT_FALSE(rb.currentHead() == rb.currentTail());
EXPECT_TRUE(rb.currentHead() != rb.currentTail());
EXPECT_FALSE(rb.currentHead() != rb.currentHead());
EXPECT_TRUE(rb.currentHead() > rb.currentTail());
EXPECT_FALSE(rb.currentTail() > rb.currentHead());
EXPECT_TRUE(rb.currentTail() < rb.currentHead());
EXPECT_FALSE(rb.currentHead() < rb.currentTail());
}
} // namespace folly } // namespace folly
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