#![cfg(loom)]
use loom::sync::atomic::{AtomicPtr, AtomicU32, Ordering};
use loom::sync::Arc;
use loom::thread;
use std::ptr::null_mut;
const SENTINEL: usize = 1;
struct Registry {
init_marker: u32,
}
fn ensure_modeled(ptr: &Arc<AtomicPtr<Registry>>, alloc_count: &Arc<AtomicU32>) -> *mut Registry {
let p = ptr.load(Ordering::Acquire);
let p_usize = p as usize;
if p_usize != 0 && p_usize != SENTINEL {
return p;
}
ensure_modeled_slow(ptr, alloc_count)
}
const MAX_SPIN_ITERS: usize = 4;
fn ensure_modeled_slow(
ptr: &Arc<AtomicPtr<Registry>>,
alloc_count: &Arc<AtomicU32>,
) -> *mut Registry {
let sentinel = SENTINEL as *mut Registry;
match ptr.compare_exchange(
null_mut(),
sentinel,
Ordering::Acquire, Ordering::Relaxed, ) {
Ok(_) => {
alloc_count.fetch_add(1, Ordering::Relaxed);
let reg = Box::new(Registry {
init_marker: 0xDEAD_BEEF,
});
let base = Box::into_raw(reg);
ptr.store(base, Ordering::Release);
base
}
Err(_) => {
for _ in 0..MAX_SPIN_ITERS {
let p = ptr.load(Ordering::Acquire);
let p_usize = p as usize;
if p_usize != 0 && p_usize != SENTINEL {
return p;
}
thread::yield_now();
}
let p = ptr.load(Ordering::Acquire);
assert!(
p as usize != 0 && p as usize != SENTINEL,
"loom: loser did not observe the real pointer within MAX_SPIN_ITERS \
iterations — a winner interleaving did not publish in time"
);
p
}
}
}
fn ensure_modeled_broken_naive(
ptr: &Arc<AtomicPtr<Registry>>,
alloc_count: &Arc<AtomicU32>,
) -> *mut Registry {
let p = ptr.load(Ordering::Acquire);
let p_usize = p as usize;
if p_usize != 0 {
return p;
}
alloc_count.fetch_add(1, Ordering::Relaxed);
let reg = Box::new(Registry {
init_marker: 0xDEAD_BEEF,
});
let base = Box::into_raw(reg);
ptr.store(base, Ordering::Release);
base
}
#[test]
fn lazy_init_exactly_once_two_threads() {
let mut builder = loom::model::Builder::new();
builder.preemption_bound = Some(3);
builder.check(|| {
let ptr: Arc<AtomicPtr<Registry>> = Arc::new(AtomicPtr::new(null_mut()));
let alloc_count = Arc::new(AtomicU32::new(0));
let p1 = Arc::clone(&ptr);
let a1 = Arc::clone(&alloc_count);
let t1 = thread::spawn(move || ensure_modeled(&p1, &a1));
let p2 = Arc::clone(&ptr);
let a2 = Arc::clone(&alloc_count);
let t2 = thread::spawn(move || ensure_modeled(&p2, &a2));
let r1 = t1.join().unwrap();
let r2 = t2.join().unwrap();
assert_eq!(r1, r2, "all threads must observe the SAME final pointer");
assert!(!r1.is_null(), "returned pointer must not be null");
assert_ne!(
r1 as usize, SENTINEL,
"returned pointer must not be sentinel"
);
let count = alloc_count.load(Ordering::Relaxed);
assert_eq!(
count, 1,
"exactly ONE thread must execute the winner branch (got {count})"
);
unsafe {
assert_eq!(
(*r1).init_marker,
0xDEAD_BEEF,
"loser must see the fully constructed Registry (Release/Acquire pair)"
);
drop(Box::from_raw(r1));
}
});
}
#[test]
fn lazy_init_exactly_once_three_threads() {
let mut builder = loom::model::Builder::new();
builder.preemption_bound = Some(1);
builder.check(|| {
let ptr: Arc<AtomicPtr<Registry>> = Arc::new(AtomicPtr::new(null_mut()));
let alloc_count = Arc::new(AtomicU32::new(0));
let (p1, a1) = (Arc::clone(&ptr), Arc::clone(&alloc_count));
let (p2, a2) = (Arc::clone(&ptr), Arc::clone(&alloc_count));
let t1 = thread::spawn(move || ensure_modeled(&p1, &a1));
let t2 = thread::spawn(move || ensure_modeled(&p2, &a2));
let r_main = ensure_modeled(&ptr, &alloc_count);
let r1 = t1.join().unwrap();
let r2 = t2.join().unwrap();
assert_eq!(
r1, r_main,
"thread 1 and main must observe the same pointer"
);
assert_eq!(
r2, r_main,
"thread 2 and main must observe the same pointer"
);
assert!(!r_main.is_null(), "returned pointer must not be null");
assert_ne!(
r_main as usize, SENTINEL,
"returned pointer must not be sentinel"
);
let count = alloc_count.load(Ordering::Relaxed);
assert_eq!(
count, 1,
"exactly ONE thread must execute the winner branch (got {count})"
);
unsafe {
assert_eq!(
(*r_main).init_marker,
0xDEAD_BEEF,
"all threads must see the fully constructed Registry"
);
drop(Box::from_raw(r_main));
}
});
}
#[test]
fn fast_path_reentry_returns_same_pointer() {
let mut builder = loom::model::Builder::new();
builder.preemption_bound = Some(3);
builder.check(|| {
let ptr: Arc<AtomicPtr<Registry>> = Arc::new(AtomicPtr::new(null_mut()));
let alloc_count = Arc::new(AtomicU32::new(0));
let p1 = Arc::clone(&ptr);
let a1 = Arc::clone(&alloc_count);
let t1 = thread::spawn(move || ensure_modeled(&p1, &a1));
let p2 = Arc::clone(&ptr);
let a2 = Arc::clone(&alloc_count);
let t2 = thread::spawn(move || {
let first = ensure_modeled(&p2, &a2);
let second = ensure_modeled(&p2, &a2);
assert_eq!(
first, second,
"fast-path re-entry must return the same pointer"
);
first
});
let r1 = t1.join().unwrap();
let r2 = t2.join().unwrap();
assert_eq!(r1, r2, "both threads must see the same pointer");
unsafe {
assert_eq!((*r1).init_marker, 0xDEAD_BEEF);
drop(Box::from_raw(r1));
}
});
}
#[test]
#[should_panic(expected = "exactly ONE thread must execute the winner branch")]
fn counterfactual_naive_no_cas_double_allocates() {
let mut builder = loom::model::Builder::new();
builder.preemption_bound = Some(3);
builder.check(|| {
let ptr: Arc<AtomicPtr<Registry>> = Arc::new(AtomicPtr::new(null_mut()));
let alloc_count = Arc::new(AtomicU32::new(0));
let p1 = Arc::clone(&ptr);
let a1 = Arc::clone(&alloc_count);
let t1 = thread::spawn(move || ensure_modeled_broken_naive(&p1, &a1));
let p2 = Arc::clone(&ptr);
let a2 = Arc::clone(&alloc_count);
let t2 = thread::spawn(move || ensure_modeled_broken_naive(&p2, &a2));
let r1 = t1.join().unwrap();
let r2 = t2.join().unwrap();
let _ = (r1, r2);
let count = alloc_count.load(Ordering::Relaxed);
assert_eq!(
count, 1,
"exactly ONE thread must execute the winner branch (got {count}, \
naive load-then-store double-allocates)"
);
});
}