fix/kpn-wedging-audit #3
@@ -444,6 +444,24 @@ private:
|
|||||||
// delivered after every value pushed before it.
|
// delivered after every value pushed before it.
|
||||||
bool take_sentinel(T& out) {
|
bool take_sentinel(T& out) {
|
||||||
if (!has_eof_.load(std::memory_order_acquire)) return false;
|
if (!has_eof_.load(std::memory_order_acquire)) return false;
|
||||||
|
// Re-check emptiness *after* observing has_eof_, not before.
|
||||||
|
//
|
||||||
|
// Callers check the ring is empty and then call this, but the producer
|
||||||
|
// can push a value and publish the sentinel in the window between those
|
||||||
|
// two steps — so the sentinel would be delivered with a real value still
|
||||||
|
// queued behind it, breaking the "sentinel is strictly last" contract
|
||||||
|
// that downstream teardown depends on. a0c4bf5 closed the variant where
|
||||||
|
// the caller's emptiness check used a stale tail_ snapshot; this is the
|
||||||
|
// one where the check is fresh but simply too early.
|
||||||
|
//
|
||||||
|
// Checking here is what makes it sound: the producer publishes the
|
||||||
|
// sentinel with a release store *after* its ring pushes, so a consumer
|
||||||
|
// that has observed has_eof_ has also observed every tail_ advance
|
||||||
|
// before it. If the ring is non-empty now, those values genuinely
|
||||||
|
// precede the sentinel and must be delivered first.
|
||||||
|
if (head_.load(std::memory_order_relaxed)
|
||||||
|
!= tail_.load(std::memory_order_acquire))
|
||||||
|
return false;
|
||||||
out = extract(std::move(eof_value_));
|
out = extract(std::move(eof_value_));
|
||||||
has_eof_.store(false, std::memory_order_release);
|
has_eof_.store(false, std::memory_order_release);
|
||||||
stats_.record_pop();
|
stats_.record_pop();
|
||||||
|
|||||||
Reference in New Issue
Block a user