use std::{
sync::Arc as StdArc,
task::{Poll, Wake},
};
use loom::{
future::block_on,
sync::{
Arc,
atomic::{AtomicBool, Ordering},
},
thread,
};
use crate::{Closed, Fan, Lock, Producer, Queue, Ref, Shared, Waiter, WaiterList, wait};
struct Flag(AtomicBool);
impl Wake for Flag {
fn wake(self: StdArc<Self>) {
self.wake_by_ref();
}
fn wake_by_ref(self: &StdArc<Self>) {
self.0.store(true, Ordering::SeqCst);
}
}
fn equals(n: u32) -> impl FnMut(&Ref<'_, u32>) -> Poll<()> + Unpin {
move |v: &Ref<'_, u32>| if **v == n { Poll::Ready(()) } else { Poll::Pending }
}
#[test]
fn write_wakes_a_parked_consumer() {
loom::model(|| {
let producer = Producer::new(0u32);
let consumer = producer.consume();
let writer = thread::spawn(move || {
*producer.write().ok().expect("open") = 1;
});
assert_eq!(block_on(consumer.wait(equals(1))), Ok(()), "the write was lost");
writer.join().unwrap();
});
}
#[test]
fn concurrent_writes_never_lose_a_wakeup() {
loom::model(|| {
let producer = Producer::new(0u32);
let consumer = producer.consume();
let second = producer.clone();
let a = thread::spawn(move || *producer.write().ok().expect("open") += 1);
let b = thread::spawn(move || *second.write().ok().expect("open") += 1);
assert_eq!(block_on(consumer.wait(equals(2))), Ok(()), "a write was lost");
a.join().unwrap();
b.join().unwrap();
});
}
#[test]
fn racing_last_producer_drops_still_close() {
loom::model(|| {
let producer = Producer::new(0u32);
let second = producer.clone();
let consumer = producer.consume();
let a = thread::spawn(move || drop(producer));
let b = thread::spawn(move || drop(second));
block_on(consumer.closed());
a.join().unwrap();
b.join().unwrap();
});
}
#[test]
fn weak_upgrade_never_resurrects_a_closed_channel() {
loom::model(|| {
let producer = Producer::new(0u32);
let weak = producer.weak();
let closer = thread::spawn(move || drop(producer));
let upgraded = weak.produce();
closer.join().unwrap();
if let Some(upgraded) = upgraded {
assert!(
upgraded.write().is_ok(),
"produce() handed back a producer on a closed channel"
);
}
});
}
#[test]
fn weak_downgrade_upgrade_races_the_last_drop() {
loom::model(|| {
let producer = Producer::new(0u32);
let weak = producer.downgrade();
let closer = thread::spawn(move || drop(producer));
let upgraded = weak.upgrade();
closer.join().unwrap();
if let Some(upgraded) = upgraded {
assert!(
upgraded.write().is_ok(),
"upgrade() handed back a producer on a closed channel"
);
}
});
}
#[test]
fn first_consumer_wakes_used() {
loom::model(|| {
let producer = Producer::new(0u32);
let second = producer.clone();
let maker = thread::spawn(move || second.consume());
assert_eq!(block_on(producer.used()), Ok(()), "the new consumer was missed");
drop(maker.join().unwrap());
});
}
#[test]
fn last_consumer_wakes_unused() {
loom::model(|| {
let producer = Producer::new(0u32);
let consumer = producer.consume();
let dropper = thread::spawn(move || drop(consumer));
assert_eq!(block_on(producer.unused()), Ok(()), "the last drop was missed");
dropper.join().unwrap();
});
}
#[test]
fn consumer_churn_resolves_unused() {
loom::model(|| {
let producer = Producer::new(0u32);
let second = producer.clone();
let churn = thread::spawn(move || drop(second.consume()));
assert_eq!(block_on(producer.unused()), Ok(()), "unused() stalled on churn");
churn.join().unwrap();
});
}
#[test]
fn shared_mutation_wakes_a_parked_handle() {
loom::model(|| {
let shared = Shared::new(0u32);
let other = shared.clone();
let writer = thread::spawn(move || *other.lock() = 1);
drop(block_on(shared.wait(equals(1))));
writer.join().unwrap();
});
}
#[test]
fn queue_push_wakes_a_parked_pop() {
loom::model(|| {
let queue = Queue::new();
let pusher = queue.clone();
let push = thread::spawn(move || pusher.try_push(1u32).unwrap());
assert_eq!(block_on(queue.pop()), Ok(1), "the push was lost");
push.join().unwrap();
});
}
#[test]
fn queue_pop_wakes_a_parked_push() {
loom::model(|| {
let queue = Queue::bounded(1);
queue.try_push(1u32).unwrap();
let popper = queue.clone();
let pop = thread::spawn(move || assert_eq!(popper.try_pop().unwrap(), Some(1)));
assert_eq!(block_on(queue.push(2)), Ok(()), "the freed slot was missed");
pop.join().unwrap();
});
}
#[test]
fn queue_close_wakes_a_parked_pop() {
loom::model(|| {
let queue = Queue::<u32>::new();
let closer = queue.clone();
let close = thread::spawn(move || closer.close());
assert_eq!(block_on(queue.pop()), Err(Closed), "the close was lost");
close.join().unwrap();
});
}
#[test]
fn a_held_wake_is_never_lost() {
loom::model(|| {
let fan = Fan::new();
let ready = Arc::new(AtomicBool::new(false));
let waker = {
let fan = fan.clone();
let ready = ready.clone();
thread::spawn(move || {
let hold = fan.hold();
ready.store(true, Ordering::SeqCst);
fan.wake();
drop(hold);
})
};
block_on(wait(|waiter| {
fan.register(waiter);
match ready.load(Ordering::SeqCst) {
true => Poll::Ready(()),
false => Poll::Pending,
}
}));
waker.join().unwrap();
});
}
#[test]
fn a_hold_covers_a_wake_waiting_for_the_list() {
loom::model(|| {
let state = Lock::new(WaiterList::new());
let fan = Fan::project(&state, |waiters| waiters);
let flag = StdArc::new(Flag(AtomicBool::new(false)));
let waiter = Waiter::new(std::task::Waker::from(flag.clone()));
waiter.register(&mut state.lock());
let state_guard = state.lock();
let waking = thread::spawn({
let fan = fan.clone();
move || fan.wake()
});
let hold = fan.hold();
drop(state_guard);
waking.join().unwrap();
assert!(!flag.0.load(Ordering::SeqCst), "wake escaped a live hold");
drop(hold);
assert!(flag.0.load(Ordering::SeqCst), "deferred wake never arrived");
});
}
#[test]
fn a_projected_hold_defers_before_relocking() {
loom::model(|| {
let state = Lock::new(WaiterList::new());
let fan = Fan::project(&state, |waiters| waiters);
let flag = StdArc::new(Flag(AtomicBool::new(false)));
let waiter = Waiter::new(std::task::Waker::from(flag.clone()));
waiter.register(&mut state.lock());
let state_guard = state.lock();
let hold = fan.hold();
fan.wake();
assert!(!flag.0.load(Ordering::SeqCst), "wake escaped a live hold");
drop(state_guard);
drop(hold);
assert!(flag.0.load(Ordering::SeqCst), "deferred wake never arrived");
});
}