#![cfg(loom)]
use loom::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
use loom::sync::Arc;
use loom::thread;
struct Node {
block_id: usize,
next: *mut Node,
}
fn push_correct(head: &AtomicPtr<Node>, block_id: usize) {
let node = Box::into_raw(Box::new(Node {
block_id,
next: core::ptr::null_mut(),
}));
loop {
let old = head.load(Ordering::Relaxed);
unsafe {
(*node).next = old;
}
match head.compare_exchange_weak(old, node, Ordering::Release, Ordering::Relaxed) {
Ok(_) => return,
Err(_) => continue,
}
}
}
fn push_naive_broken(head: &AtomicPtr<Node>, block_id: usize) {
let node = Box::into_raw(Box::new(Node {
block_id,
next: core::ptr::null_mut(),
}));
let old = head.load(Ordering::Relaxed);
unsafe {
(*node).next = old;
}
head.store(node, Ordering::Release);
}
fn drain(head: &AtomicPtr<Node>) -> Vec<usize> {
let mut cur = head.swap(core::ptr::null_mut(), Ordering::Acquire);
let mut ids = Vec::new();
while !cur.is_null() {
let node = unsafe { Box::from_raw(cur) };
ids.push(node.block_id);
cur = node.next;
}
ids
}
fn cleanup(head: &AtomicPtr<Node>) {
let mut cur = head.load(Ordering::Relaxed);
while !cur.is_null() {
let node = unsafe { Box::from_raw(cur) };
cur = node.next;
}
head.store(core::ptr::null_mut(), Ordering::Relaxed);
}
#[test]
fn correct_push_never_loses_blocks() {
let mut builder = loom::model::Builder::new();
builder.preemption_bound = Some(3);
builder.check(|| {
let head = Arc::new(AtomicPtr::new(core::ptr::null_mut()));
let total_pushed = Arc::new(AtomicUsize::new(0));
let h1 = Arc::clone(&head);
let tp1 = Arc::clone(&total_pushed);
let t1 = thread::spawn(move || {
push_correct(&h1, 1);
tp1.fetch_add(1, Ordering::Relaxed);
});
let h2 = Arc::clone(&head);
let tp2 = Arc::clone(&total_pushed);
let t2 = thread::spawn(move || {
push_correct(&h2, 2);
tp2.fetch_add(1, Ordering::Relaxed);
});
t1.join().unwrap();
t2.join().unwrap();
let ids = drain(&head);
let pushed = total_pushed.load(Ordering::Relaxed);
assert_eq!(
ids.len(),
pushed,
"lost or duplicated blocks: expected {pushed}, got {}",
ids.len()
);
assert!(ids.contains(&1), "block 1 lost");
assert!(ids.contains(&2), "block 2 lost");
cleanup(&head);
});
}
#[test]
fn owner_and_remote_push_both_visible() {
let mut builder = loom::model::Builder::new();
builder.preemption_bound = Some(3);
builder.check(|| {
let head = Arc::new(AtomicPtr::new(core::ptr::null_mut()));
let h1 = Arc::clone(&head);
let remote = thread::spawn(move || {
push_correct(&h1, 10);
});
push_correct(&head, 20);
remote.join().unwrap();
let ids = drain(&head);
assert_eq!(ids.len(), 2, "expected 2 blocks, got {}", ids.len());
assert!(ids.contains(&10), "remote block 10 lost");
assert!(ids.contains(&20), "owner block 20 lost");
cleanup(&head);
});
}
#[test]
#[should_panic(expected = "lost")]
fn counterfactual_naive_push_loses_blocks() {
let mut builder = loom::model::Builder::new();
builder.preemption_bound = Some(3);
builder.check(|| {
let head = Arc::new(AtomicPtr::new(core::ptr::null_mut()));
let h1 = Arc::clone(&head);
let t1 = thread::spawn(move || {
push_naive_broken(&h1, 1);
});
let h2 = Arc::clone(&head);
let t2 = thread::spawn(move || {
push_naive_broken(&h2, 2);
});
t1.join().unwrap();
t2.join().unwrap();
let ids = drain(&head);
assert!(
ids.len() == 2,
"lost block: expected 2, got {} (naive push is broken)",
ids.len()
);
cleanup(&head);
});
}
#[test]
fn drain_empty_is_noop() {
let mut builder = loom::model::Builder::new();
builder.preemption_bound = Some(3);
builder.check(|| {
let head = Arc::new(AtomicPtr::new(core::ptr::null_mut()));
let ids = drain(&head);
assert!(ids.is_empty(), "drain of empty stack should be empty");
cleanup(&head);
});
}