#![cfg(loom)]
use loom::sync::Arc;
use loom::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use loom::thread;
const XMASK: usize = 3;
const NORMAL: usize = 0;
const DELAYED: usize = 1;
const FREEING: usize = 2;
const NEVER: usize = 3;
struct Model {
xthread: AtomicUsize,
heap_delayed: AtomicUsize,
heap_alive: AtomicBool,
next: [AtomicUsize; 8],
}
impl Model {
fn new(initial_flag: usize) -> Model {
Model {
xthread: AtomicUsize::new(initial_flag),
heap_delayed: AtomicUsize::new(0),
heap_alive: AtomicBool::new(true),
next: Default::default(),
}
}
fn remote_free(&self, id: usize) {
loop {
let x = self.xthread.load(Ordering::Acquire);
match x & XMASK {
DELAYED => {
if self
.xthread
.compare_exchange(
x,
(x & !XMASK) | FREEING,
Ordering::AcqRel,
Ordering::Relaxed,
)
.is_ok()
{
assert!(
self.heap_alive.load(Ordering::Acquire),
"UAF: delayed push into a dead heap"
);
loop {
let d = self.heap_delayed.load(Ordering::Acquire);
self.next[id].store(d, Ordering::Relaxed);
if self
.heap_delayed
.compare_exchange(d, id << 8, Ordering::AcqRel, Ordering::Relaxed)
.is_ok()
{
break;
}
}
loop {
let y = self.xthread.load(Ordering::Acquire);
if self
.xthread
.compare_exchange(
y,
(y & !XMASK) | DELAYED,
Ordering::AcqRel,
Ordering::Relaxed,
)
.is_ok()
{
break;
}
}
return;
}
}
FREEING => loom::thread::yield_now(),
flag => {
self.next[id].store(x >> 8 << 8, Ordering::Relaxed); if self
.xthread
.compare_exchange(x, (id << 8) | flag, Ordering::AcqRel, Ordering::Relaxed)
.is_ok()
{
return;
}
}
}
}
}
fn owner_collect(&self) -> usize {
let mut taken = 0;
loop {
let x = self.xthread.load(Ordering::Acquire);
if x >> 8 == 0 {
return taken;
}
if self
.xthread
.compare_exchange(x, x & XMASK, Ordering::AcqRel, Ordering::Relaxed)
.is_ok()
{
let mut id = x >> 8;
while id != 0 {
taken += 1;
id = self.next[id].load(Ordering::Relaxed) >> 8;
}
return taken;
}
}
}
fn owner_drain_delayed(&self) -> usize {
let d = self.heap_delayed.swap(0, Ordering::AcqRel);
let mut n = 0;
let mut id = d >> 8;
while id != 0 {
n += 1;
id = self.next[id].load(Ordering::Relaxed) >> 8;
}
n
}
fn abandon(&self) -> usize {
loop {
let x = self.xthread.load(Ordering::Acquire);
if x & XMASK == FREEING {
loom::thread::yield_now();
continue;
}
if self
.xthread
.compare_exchange(x, (x & !XMASK) | NEVER, Ordering::AcqRel, Ordering::Relaxed)
.is_ok()
{
break;
}
}
let drained = self.owner_drain_delayed();
self.heap_alive.store(false, Ordering::Release);
drained
}
}
#[test]
fn delayed_push_vs_abandon() {
loom::model(|| {
let m = Arc::new(Model::new(DELAYED));
let m1 = m.clone();
let t1 = thread::spawn(move || m1.remote_free(1));
let ma = m.clone();
let ta = thread::spawn(move || ma.abandon());
t1.join().unwrap();
let drained_at_abandon = ta.join().unwrap();
let on_page = m.owner_collect();
let late_delayed = m.owner_drain_delayed();
assert_eq!(
drained_at_abandon + on_page + late_delayed,
1,
"protocol lost the block"
);
assert_eq!(m.xthread.load(Ordering::Relaxed) & XMASK, NEVER);
});
}
#[test]
fn delayed_push_vs_abandon_two_remotes_extended() {
if std::env::var_os("LOOM_EXTENDED").is_none() {
eprintln!("skipped (set LOOM_EXTENDED=1): 2-remote abandon soak");
return;
}
let mut b = loom::model::Builder::new();
b.preemption_bound = Some(2);
b.max_branches = 100_000;
b.check(|| {
let m = Arc::new(Model::new(DELAYED));
let m1 = m.clone();
let m2 = m.clone();
let t1 = thread::spawn(move || m1.remote_free(1));
let t2 = thread::spawn(move || m2.remote_free(2));
let ma = m.clone();
let ta = thread::spawn(move || ma.abandon());
t1.join().unwrap();
t2.join().unwrap();
let drained_at_abandon = ta.join().unwrap();
let on_page = m.owner_collect();
let late_delayed = m.owner_drain_delayed();
assert_eq!(
drained_at_abandon + on_page + late_delayed,
2,
"protocol lost a block"
);
});
}
#[test]
fn normal_push_vs_collect() {
loom::model(|| {
let m = Arc::new(Model::new(NORMAL));
let m1 = m.clone();
let m2 = m.clone();
let t1 = thread::spawn(move || m1.remote_free(1));
let t2 = thread::spawn(move || m2.remote_free(2));
let mo = m.clone();
let to = thread::spawn(move || mo.owner_collect());
let c1 = to.join().unwrap();
t1.join().unwrap();
t2.join().unwrap();
let c2 = m.owner_collect();
assert_eq!(c1 + c2, 2, "collect lost a block");
});
}
#[test]
fn park_unpark_vs_remote() {
loom::model(|| {
let m = Arc::new(Model::new(NORMAL));
let mr = m.clone();
let tr = thread::spawn(move || mr.remote_free(1));
let mo = m.clone();
let tp = thread::spawn(move || {
loop {
let x = mo.xthread.load(Ordering::Acquire);
if x & XMASK == FREEING {
loom::thread::yield_now();
continue;
}
if mo
.xthread
.compare_exchange(
x,
(x & !XMASK) | DELAYED,
Ordering::AcqRel,
Ordering::Relaxed,
)
.is_ok()
{
break;
}
}
});
tr.join().unwrap();
tp.join().unwrap();
let total = m.owner_collect() + m.owner_drain_delayed();
assert_eq!(total, 1, "block lost across park transition");
});
}