#![cfg(loom)]
#![allow(missing_docs)]
use loom::sync::Arc;
use loom::sync::Mutex;
use loom::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use loom::thread;
struct Engine {
queue: Mutex<Vec<u32>>,
shutdown: AtomicBool,
drained: AtomicU64,
}
impl Engine {
fn new() -> Self {
Self {
queue: Mutex::new(Vec::new()),
shutdown: AtomicBool::new(false),
drained: AtomicU64::new(0),
}
}
fn ingest(&self, event_id: u32) {
self.queue.lock().unwrap().push(event_id);
}
fn flush_until_shutdown_and_empty(&self) {
loop {
loop {
let popped = {
let mut q = self.queue.lock().unwrap();
q.pop()
};
match popped {
Some(_) => {
self.drained.fetch_add(1, Ordering::AcqRel);
}
None => break,
}
}
if self.shutdown.load(Ordering::Acquire) {
let empty = self.queue.lock().unwrap().is_empty();
if empty {
return;
}
}
thread::yield_now();
}
}
fn request_shutdown(&self) {
self.shutdown.store(true, Ordering::Release);
}
}
#[test]
fn proof_no_events_lost_single_producer() {
loom::model(|| {
let engine = Arc::new(Engine::new());
let producer_engine = engine.clone();
let producer = thread::spawn(move || {
producer_engine.ingest(1);
producer_engine.ingest(2);
producer_engine.request_shutdown();
});
let flusher_engine = engine.clone();
let flusher = thread::spawn(move || {
flusher_engine.flush_until_shutdown_and_empty();
});
producer.join().unwrap();
flusher.join().unwrap();
assert_eq!(engine.drained.load(Ordering::SeqCst), 2);
});
}
#[test]
fn proof_no_events_lost_multi_producer() {
loom::model(|| {
let engine = Arc::new(Engine::new());
let producers_done = Arc::new(AtomicU64::new(0));
let a_engine = engine.clone();
let a_done = producers_done.clone();
let a = thread::spawn(move || {
a_engine.ingest(1);
a_done.fetch_add(1, Ordering::Release);
});
let b_engine = engine.clone();
let b_done = producers_done.clone();
let b = thread::spawn(move || {
b_engine.ingest(2);
b_done.fetch_add(1, Ordering::Release);
});
let flusher_engine = engine.clone();
let flusher = thread::spawn(move || {
flusher_engine.flush_until_shutdown_and_empty();
});
a.join().unwrap();
b.join().unwrap();
assert_eq!(producers_done.load(Ordering::Acquire), 2);
engine.request_shutdown();
flusher.join().unwrap();
assert_eq!(engine.drained.load(Ordering::SeqCst), 2);
});
}
#[test]
fn proof_session_id_monotonicity_under_concurrent_producers() {
loom::model(|| {
let counter = Arc::new(AtomicU64::new(0));
let c1 = counter.clone();
let t1 =
thread::spawn(move || c1.fetch_add(1, Ordering::AcqRel));
let c2 = counter.clone();
let t2 =
thread::spawn(move || c2.fetch_add(1, Ordering::AcqRel));
let id1 = t1.join().unwrap();
let id2 = t2.join().unwrap();
assert_ne!(id1, id2);
assert!(id1 == 0 || id1 == 1);
assert!(id2 == 0 || id2 == 1);
assert_eq!(counter.load(Ordering::SeqCst), 2);
});
}