#![cfg(loom)]
use loom::sync::atomic::{AtomicU32, AtomicUsize, Ordering};
use loom::sync::Arc;
use loom::thread;
const RING_SLOT_EMPTY: u32 = u32::MAX;
const CAP: usize = 4;
struct RingModel {
head: AtomicU32,
tail: AtomicU32,
slots: [AtomicU32; CAP],
}
impl RingModel {
fn new() -> Arc<Self> {
let r = Arc::new(RingModel {
head: AtomicU32::new(0),
tail: AtomicU32::new(0),
slots: std::array::from_fn(|_| AtomicU32::new(RING_SLOT_EMPTY)),
});
r
}
fn push(&self, offset: u32) -> Result<(), ()> {
loop {
let t = self.tail.load(Ordering::Relaxed);
let h = self.head.load(Ordering::Acquire);
if t.wrapping_sub(h) >= CAP as u32 {
return Err(()); }
match self.tail.compare_exchange_weak(
t,
t.wrapping_add(1),
Ordering::AcqRel,
Ordering::Relaxed,
) {
Ok(_) => {
self.slots[(t as usize) % CAP].store(offset, Ordering::Release);
return Ok(());
}
Err(_) => continue,
}
}
}
fn drain<F: FnMut(u32)>(&self, mut reclaim: F) {
let t = self.tail.load(Ordering::Acquire);
let mut h = self.head.load(Ordering::Relaxed);
while h != t {
let slot = &self.slots[(h as usize) % CAP];
let off = slot.load(Ordering::Acquire);
if off == RING_SLOT_EMPTY {
break; }
reclaim(off);
slot.store(RING_SLOT_EMPTY, Ordering::Relaxed); h = h.wrapping_add(1);
}
self.head.store(h, Ordering::Release);
}
fn drain_broken_no_break<F: FnMut(u32)>(&self, mut reclaim: F) {
let t = self.tail.load(Ordering::Acquire);
let mut h = self.head.load(Ordering::Relaxed);
while h != t {
let slot = &self.slots[(h as usize) % CAP];
let off = slot.load(Ordering::Acquire);
if off != RING_SLOT_EMPTY {
reclaim(off);
slot.store(RING_SLOT_EMPTY, Ordering::Relaxed);
}
h = h.wrapping_add(1);
}
self.head.store(h, Ordering::Release);
}
fn drain_broken_no_clear<F: FnMut(u32)>(&self, mut reclaim: F) {
let t = self.tail.load(Ordering::Acquire);
let mut h = self.head.load(Ordering::Relaxed);
while h != t {
let slot = &self.slots[(h as usize) % CAP];
let off = slot.load(Ordering::Acquire);
if off == RING_SLOT_EMPTY {
break;
}
reclaim(off);
h = h.wrapping_add(1);
}
self.head.store(h, Ordering::Release);
}
}
#[test]
fn correct_ring_never_loses_or_duplicates() {
let mut builder = loom::model::Builder::new();
builder.preemption_bound = Some(3);
builder.check(|| {
let ring = RingModel::new();
let ring_a = Arc::clone(&ring);
let ta = thread::spawn(move || {
while ring_a.push(10).is_err() {}
});
let ring_b = Arc::clone(&ring);
let tb = thread::spawn(move || while ring_b.push(20).is_err() {});
ta.join().unwrap();
tb.join().unwrap();
let mut got_a = 0u32;
let mut got_b = 0u32;
ring.drain(|off| {
if off == 10 {
got_a += 1;
} else if off == 20 {
got_b += 1;
}
});
assert_eq!(
got_a, 1,
"offset 10 reclaimed {got_a} times (expected 1) — loss or duplication"
);
assert_eq!(
got_b, 1,
"offset 20 reclaimed {got_b} times (expected 1) — loss or duplication"
);
});
}
#[test]
#[should_panic(expected = "lost")]
fn counterfactual_drain_without_break_loses_offset() {
let mut builder = loom::model::Builder::new();
builder.preemption_bound = Some(3);
builder.check(|| {
let ring = RingModel::new();
let pushed_ok = Arc::new(AtomicUsize::new(0));
let reclaimed = Arc::new(AtomicUsize::new(0));
let ring_a = Arc::clone(&ring);
let pushed_a = Arc::clone(&pushed_ok);
let ta = thread::spawn(move || {
if ring_a.push(10).is_ok() {
pushed_a.fetch_add(1, Ordering::Relaxed);
}
});
let ring_b = Arc::clone(&ring);
let pushed_b = Arc::clone(&pushed_ok);
let tb = thread::spawn(move || {
if ring_b.push(20).is_ok() {
pushed_b.fetch_add(1, Ordering::Relaxed);
}
});
let reclaimed_c = Arc::clone(&reclaimed);
let ring_c = Arc::clone(&ring);
let pushed_c = Arc::clone(&pushed_ok);
let tc = thread::spawn(move || {
loop {
ring_c.drain_broken_no_break(|_| {
reclaimed_c.fetch_add(1, Ordering::Relaxed);
});
if pushed_c.load(Ordering::Acquire) >= 2 {
break;
}
thread::yield_now();
}
for _ in 0..2 {
ring_c.drain_broken_no_break(|_| {
reclaimed_c.fetch_add(1, Ordering::Relaxed);
});
}
});
ta.join().unwrap();
tb.join().unwrap();
tc.join().unwrap();
let pushed = pushed_ok.load(Ordering::Acquire);
let got = reclaimed.load(Ordering::Acquire);
assert!(
got == pushed,
"lost offset: reclaimed {got} of {pushed} successfully-pushed \
(drain-without-break loses a not-yet-published reservation)"
);
});
}
#[test]
#[should_panic(expected = "duplicate")]
fn counterfactual_drain_without_clear_duplicates_on_wrap() {
let mut builder = loom::model::Builder::new();
builder.preemption_bound = Some(3);
builder.check(|| {
let ring = RingModel::new();
let reclaimed_counts: Arc<Vec<AtomicU32>> =
Arc::new((0..200).map(|_| AtomicU32::new(0)).collect());
let ring_p = Arc::clone(&ring);
let tp = thread::spawn(move || {
for off in 100..105 {
while ring_p.push(off).is_err() {
thread::yield_now();
}
}
});
let reclaimed_c = Arc::clone(&reclaimed_counts);
let ring_c = Arc::clone(&ring);
let tc = thread::spawn(move || {
for _ in 0..8 {
ring_c.drain_broken_no_clear(|off| {
if off >= 100 && off < 200 {
reclaimed_c[off as usize - 100].fetch_add(1, Ordering::Relaxed);
}
});
thread::yield_now();
}
});
tp.join().unwrap();
tc.join().unwrap();
let mut max_dup = 0u32;
for c in reclaimed_counts.iter() {
let v = c.load(Ordering::Acquire);
if v > max_dup {
max_dup = v;
}
}
assert!(
max_dup <= 1,
"duplicate: an offset reclaimed {max_dup} times (drain-without-clear \
leaves a stale offset re-reclaimed on wrap)"
);
});
}
#[test]
fn drain_empty_ring_is_noop() {
let mut builder = loom::model::Builder::new();
builder.preemption_bound = Some(3);
builder.check(|| {
let ring = RingModel::new();
let mut count = 0u32;
ring.drain(|_| {
count += 1;
});
assert_eq!(count, 0, "drain of empty ring reclaimed something");
});
}