#![cfg(feature = "loom")]
use loom::model::Builder;
use loom::thread;
use swmr_cell::SwmrCell;
#[test]
fn loom_concurrent_readers() {
loom::model(|| {
let cell = SwmrCell::new(42i32);
let mut handles = vec![];
for _ in 0..2 {
let local = cell.local();
let handle = thread::spawn(move || {
let guard = local.pin();
assert_eq!(*guard, 42);
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
});
}
#[test]
fn loom_single_writer_multi_reader() {
loom::model(|| {
let mut cell = SwmrCell::new(1i32);
let local = cell.local();
let reader_handle = thread::spawn(move || {
let guard = local.pin();
let value = *guard;
assert!(value == 1 || value == 2);
});
cell.store(2i32);
cell.collect();
reader_handle.join().unwrap();
});
}
#[test]
fn loom_nested_loads() {
loom::model(|| {
let cell = SwmrCell::new(100i32);
let local = cell.local();
let handle = thread::spawn(move || {
let guard1 = local.pin();
assert_eq!(*guard1, 100);
let guard2 = local.pin();
assert_eq!(*guard2, 100);
assert_eq!(*guard1, 100);
drop(guard2);
assert_eq!(*guard1, 100);
});
handle.join().unwrap();
});
}
#[test]
fn loom_gc_safety() {
loom::model(|| {
let mut cell = SwmrCell::new(1i32);
let local = cell.local();
let reader_handle = thread::spawn(move || {
let guard = local.pin();
let value = *guard;
assert!(value >= 1 && value <= 3);
thread::yield_now();
assert_eq!(*guard, value);
});
cell.store(2i32);
cell.collect();
cell.store(3i32);
cell.collect();
reader_handle.join().unwrap();
});
}
#[test]
fn loom_multiple_stores() {
loom::model(|| {
let mut cell = SwmrCell::new(1i32);
let local = cell.local();
cell.store(2i32);
cell.store(3i32);
cell.collect();
let guard = local.pin();
assert_eq!(*guard, 3);
});
}
#[test]
fn loom_epoch_advancement() {
loom::model(|| {
let mut cell = SwmrCell::new(0i32);
let local = cell.local();
let reader_handle = thread::spawn(move || {
for _ in 0..2 {
let guard = local.pin();
assert!(*guard >= 0);
drop(guard);
}
});
cell.collect();
cell.collect();
reader_handle.join().unwrap();
});
}
#[test]
fn loom_store_load_consistency() {
loom::model(|| {
let mut cell = SwmrCell::new(1i32);
let local = cell.local();
cell.store(42i32);
let guard = local.pin();
assert_eq!(*guard, 42);
});
}
#[test]
fn loom_sequential_writer_ops() {
loom::model(|| {
let mut cell = SwmrCell::new(1i32);
let local = cell.local();
let reader_thread = thread::spawn(move || {
let guard = local.pin();
let val = *guard;
assert!(val >= 1 && val <= 3);
});
cell.store(2i32);
cell.collect();
cell.store(3i32);
cell.collect();
reader_thread.join().unwrap();
});
}
#[test]
fn loom_multiple_swmr_cells() {
loom::model(|| {
let mut w1 = SwmrCell::new(10i32);
let mut w2 = SwmrCell::new(20i32);
let r1 = w1.local();
let r2 = w2.local();
let local = thread::spawn(move || {
let g1 = r1.pin();
let g2 = r2.pin();
let v1 = *g1;
let v2 = *g2;
assert!(v1 == 10 || v1 == 11);
assert!(v2 == 20 || v2 == 21);
});
w1.store(11i32);
w2.store(21i32);
w1.collect(); w2.collect();
local.join().unwrap();
});
}
#[test]
fn loom_fast_load_drop_cycles() {
loom::model(|| {
let mut cell = SwmrCell::new(0i32);
let local = cell.local();
let reader_thread = thread::spawn(move || {
for _ in 0..2 {
let guard = local.pin();
let _val = *guard;
drop(guard);
thread::yield_now();
}
});
cell.store(1i32);
cell.collect();
reader_thread.join().unwrap();
});
}
#[test]
fn loom_gc_with_no_active_readers() {
loom::model(|| {
let mut cell = SwmrCell::new(1i32);
let local = cell.local();
cell.store(2i32);
cell.store(3i32);
cell.store(4i32);
cell.collect();
let guard = local.pin();
assert_eq!(*guard, 4);
});
}
#[test]
fn loom_reader_drop() {
loom::model(|| {
let mut cell = SwmrCell::new(1i32);
let local = cell.local();
let reader_thread = thread::spawn(move || {
{
let guard = local.pin();
let val = *guard;
assert!(val == 1 || val == 2);
}
});
thread::yield_now();
cell.store(2i32);
cell.collect();
reader_thread.join().unwrap();
});
}
#[test]
fn loom_reader_across_epochs() {
let mut builder = Builder::new();
builder.preemption_bound = Some(3);
builder.check(|| {
let mut cell = SwmrCell::new(1i32);
let local = cell.local();
let reader_thread = thread::spawn(move || {
let guard = local.pin();
let initial = *guard;
thread::yield_now();
let val1 = *guard;
thread::yield_now();
let val2 = *guard;
thread::yield_now();
assert_eq!(val1, initial); assert_eq!(val2, initial);
});
cell.collect();
cell.store(2i32);
cell.collect();
cell.store(3i32);
cell.collect();
reader_thread.join().unwrap();
});
}
#[test]
fn loom_three_readers_one_writer() {
let mut builder = Builder::new();
builder.preemption_bound = Some(2);
builder.check(|| {
let mut cell = SwmrCell::new(0i32);
let mut readers = vec![];
for _ in 0..3 {
let r = cell.local();
let handle = thread::spawn(move || {
let guard = r.pin();
assert!(*guard <= 5);
});
readers.push(handle);
}
cell.store(5i32);
cell.collect();
for handle in readers {
handle.join().unwrap();
}
});
}
#[test]
fn loom_interleaved_load_drop() {
let mut builder = Builder::new();
builder.preemption_bound = Some(4);
builder.check(|| {
let mut cell = SwmrCell::new(100i32);
let mut handles = vec![];
for _ in 0..2 {
let r = cell.local();
let handle = thread::spawn(move || {
{
let guard = r.pin();
let _val = *guard;
}
thread::yield_now();
{
let guard = r.pin();
let _val = *guard;
}
});
handles.push(handle);
}
cell.collect();
for h in handles {
h.join().unwrap();
}
});
}
#[test]
fn loom_store_collect_read_race() {
loom::model(|| {
let mut cell = SwmrCell::new(1i32);
let local = cell.local();
let reader_thread = thread::spawn(move || {
let guard = local.pin();
let val = *guard;
assert!(val == 1 || val == 2);
});
cell.store(2i32);
cell.collect();
reader_thread.join().unwrap();
});
}
#[test]
fn loom_builder_custom_threshold() {
loom::model(|| {
let mut cell = SwmrCell::builder()
.auto_reclaim_threshold(Some(2))
.build(1i32);
let local = cell.local();
cell.store(2i32);
let guard = local.pin();
assert_eq!(*guard, 2);
});
}
#[test]
fn loom_builder_no_auto_reclaim() {
loom::model(|| {
let mut cell = SwmrCell::builder().auto_reclaim_threshold(None).build(1i32);
let local = cell.local();
cell.store(2i32);
cell.store(3i32);
cell.collect();
let guard = local.pin();
assert_eq!(*guard, 3);
});
}
#[test]
fn loom_different_pin_lifetimes() {
let mut builder = Builder::new();
builder.preemption_bound = Some(4);
builder.check(|| {
let mut cell = SwmrCell::new(1i32);
let r1 = cell.local();
let t1 = thread::spawn(move || {
{
let _guard = r1.pin();
} });
let r2 = cell.local();
let t2 = thread::spawn(move || {
let guard = r2.pin();
let val = *guard;
thread::yield_now();
assert_eq!(val, *guard);
});
cell.store(2i32);
cell.collect();
t1.join().unwrap();
t2.join().unwrap();
});
}
#[test]
fn loom_multiple_collections_no_stores() {
let mut builder = Builder::new();
builder.preemption_bound = Some(3);
builder.check(|| {
let mut cell = SwmrCell::new(42i32);
let r = cell.local();
let t = thread::spawn(move || {
let guard = r.pin();
assert_eq!(*guard, 42);
});
cell.collect();
cell.collect();
cell.collect();
t.join().unwrap();
});
}
#[test]
fn loom_alternating_store_collect() {
loom::model(|| {
let mut cell = SwmrCell::new(0i32);
let r = cell.local();
let t = thread::spawn(move || {
let guard = r.pin();
let val = *guard;
assert!(val <= 2);
});
cell.store(1i32);
cell.collect();
cell.store(2i32);
cell.collect();
t.join().unwrap();
});
}
#[test]
fn loom_multiple_guards_same_reader() {
loom::model(|| {
let cell = SwmrCell::new(77i32);
let local = cell.local();
let handle = thread::spawn(move || {
let guard1 = local.pin();
let guard2 = local.pin();
assert_eq!(*guard1, 77);
assert_eq!(*guard2, 77);
drop(guard2);
assert_eq!(*guard1, 77);
});
handle.join().unwrap();
});
}
#[test]
fn loom_reader_monotonic_observation() {
let mut builder = Builder::new();
builder.preemption_bound = Some(3);
builder.check(|| {
let mut cell = SwmrCell::new(1i32);
let r = cell.local();
let t = thread::spawn(move || {
let guard1 = r.pin();
let val1 = *guard1;
drop(guard1);
thread::yield_now();
let guard2 = r.pin();
let val2 = *guard2;
drop(guard2);
assert!(val1 >= 1 && val1 <= 3);
assert!(val2 >= 1 && val2 <= 3);
});
cell.store(2i32);
cell.collect();
cell.store(3i32);
cell.collect();
t.join().unwrap();
});
}
#[test]
fn loom_reader_holds_guard_during_updates() {
loom::model(|| {
let mut cell = SwmrCell::new(0i32);
let r = cell.local();
let t = thread::spawn(move || {
let guard = r.pin();
let initial_value = *guard;
thread::yield_now();
thread::yield_now();
assert_eq!(*guard, initial_value);
assert!(initial_value >= 0 && initial_value <= 3);
});
cell.store(1i32);
cell.store(2i32);
cell.store(3i32);
t.join().unwrap();
});
}
#[test]
fn loom_use_after_free() {
struct Data {
dropped: bool,
}
fn new_data() -> Data {
Data { dropped: false }
}
impl Drop for Data {
fn drop(&mut self) {
self.dropped = true;
}
}
loom::model(|| {
let mut cell = SwmrCell::new(new_data());
let r = cell.local();
let t = thread::spawn(move || {
let guard = r.pin();
assert!(!guard.dropped);
});
const WRITE_NUM: usize = 4;
for _ in 0..WRITE_NUM {
cell.store(new_data());
}
cell.collect();
t.join().unwrap();
});
}