feat: a retained slot can be backfilled without pushing
This commit is contained in:
1 parent
2ebf2be571
commit
1ae72d4903
2 files changed
+42
No files matched your search
@@ -64,6 +64,24 @@ class RingBuffer {
|
||||
for (u64 i = 0; i < n; ++i) push(vs[i]);
|
||||
}
|
||||
|
||||
/// Producer thread only: rewrites the slot at absolute index `absolute`, without pushing.
|
||||
/// Returns false when that index is not retained -- already overwritten, or not yet
|
||||
/// produced -- in which case nothing is written.
|
||||
///
|
||||
/// This is for *backfill*: a slot pushed as a placeholder and corrected when the data that
|
||||
/// belongs there arrives late. It is the same race class as `push()` -- one relaxed store
|
||||
/// into a slot a consumer may be reading, and T is lock-free by the class's static_assert,
|
||||
/// so a reader sees the old value or the new one and never a torn one. It deliberately does
|
||||
/// not touch `produced_`: the count fixes which indices a snapshot reads, and an amend
|
||||
/// changes a value inside that window rather than extending it.
|
||||
bool amend(u64 absolute, T v) {
|
||||
const u64 p = produced_.load(std::memory_order_relaxed);
|
||||
if (absolute >= p) return false;
|
||||
if (p > cap_ && absolute < p - cap_) return false;
|
||||
buf_[static_cast<usize>(absolute & mask_)].store(v, std::memory_order_relaxed);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Consumer: retained element count, saturating at capacity().
|
||||
u64 size() const noexcept {
|
||||
const u64 p = produced_.load(std::memory_order_acquire);
|
||||
|
||||
@@ -35,3 +35,27 @@ PLOT_TEST(ring_capacity_rounds_up_to_a_power_of_two) {
|
||||
PLOT_CHECK_NEAR(r[0], 872.0); // oldest retained
|
||||
PLOT_CHECK_NEAR(r[127], 999.0); // newest
|
||||
}
|
||||
|
||||
PLOT_TEST(ring_amend_rewrites_a_retained_slot) {
|
||||
// Backfill: a slot pushed as a placeholder is rewritten once the data that belongs there
|
||||
// arrives. The index is absolute, because that is the only coordinate a late packet can
|
||||
// compute -- a retained-window index would have moved by the time it got here.
|
||||
plot::data::RingBuffer<f32> r(8);
|
||||
for (int i = 0; i < 6; ++i) r.push(f32(i));
|
||||
PLOT_CHECK(r.amend(3, 42.0f));
|
||||
PLOT_CHECK_NEAR(r[3], 42.0);
|
||||
PLOT_CHECK(r.produced() == 6); // an amend is not a push
|
||||
}
|
||||
|
||||
PLOT_TEST(ring_amend_refuses_an_index_the_ring_no_longer_holds) {
|
||||
// The window slides on the producer, so a late amend may arrive after its slot was
|
||||
// overwritten. Writing it anyway would corrupt whatever sample now owns that slot, so the
|
||||
// bounds test is the whole safety of the operation and the caller reads the result.
|
||||
plot::data::RingBuffer<f32> r(8);
|
||||
for (int i = 0; i < 20; ++i) r.push(f32(i));
|
||||
PLOT_CHECK(r.base() == 12);
|
||||
PLOT_CHECK(!r.amend(3, 42.0f)); // aged out
|
||||
PLOT_CHECK(!r.amend(20, 42.0f)); // never produced
|
||||
PLOT_CHECK_NEAR(r[0], 12.0); // and nothing moved
|
||||
PLOT_CHECK_NEAR(r[7], 19.0);
|
||||
}
|
||||
Reference in new issue
Block a user