#![cfg(loom)]
use loom::sync::Arc;
use loom::sync::atomic::{AtomicU8, Ordering};
use loom::thread;
const MAX_FREQ: u8 = 3;
fn bump_freq(freq: &AtomicU8) {
let _ = freq.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |f| {
if f < MAX_FREQ { Some(f + 1) } else { None }
});
}
#[test]
fn bump_freq_two_readers_no_loss() {
loom::model(|| {
let freq = Arc::new(AtomicU8::new(0));
let h1 = {
let freq = freq.clone();
thread::spawn(move || bump_freq(&freq))
};
let h2 = {
let freq = freq.clone();
thread::spawn(move || bump_freq(&freq))
};
h1.join().unwrap();
h2.join().unwrap();
let v = freq.load(Ordering::Relaxed);
assert!(
v == 1 || v == 2,
"freq ended at {v}; expected 1 or 2 after two concurrent bumps from 0"
);
});
}
#[test]
fn bump_freq_saturates_at_max() {
loom::model(|| {
let freq = Arc::new(AtomicU8::new(MAX_FREQ - 1));
let mut handles = Vec::with_capacity(3);
for _ in 0..3 {
let freq = freq.clone();
handles.push(thread::spawn(move || bump_freq(&freq)));
}
for h in handles {
h.join().unwrap();
}
let v = freq.load(Ordering::Relaxed);
assert_eq!(
v, MAX_FREQ,
"freq must saturate at MAX_FREQ under any interleaving"
);
});
}
#[test]
fn freq_load_observes_bump_through_release_acquire_fence() {
use loom::sync::atomic::AtomicUsize;
loom::model(|| {
let freq = Arc::new(AtomicU8::new(0));
let lock = Arc::new(AtomicUsize::new(0));
let reader = {
let freq = freq.clone();
let lock = lock.clone();
thread::spawn(move || {
lock.store(1, Ordering::Release);
bump_freq(&freq);
lock.store(2, Ordering::Release);
})
};
let writer = {
let freq = freq.clone();
let lock = lock.clone();
thread::spawn(move || {
while lock.load(Ordering::Acquire) != 2 {
loom::thread::yield_now();
}
let v = freq.load(Ordering::Relaxed);
assert_eq!(
v, 1,
"writer must observe the reader's bump after the lock fence"
);
})
};
reader.join().unwrap();
writer.join().unwrap();
});
}