use super::{shard::STOPPED, Guard, Inner};
use crate::Interrupt;
use loom::{
sync::atomic::{AtomicUsize, Ordering},
thread,
};
use std::{
future::{pending, Future, Pending},
pin::Pin,
sync::Arc,
task::{Context, Poll, Wake, Waker},
};
#[derive(Default)]
struct Wakes(AtomicUsize);
impl Wake for Wakes {
fn wake(self: Arc<Self>) {
self.0.fetch_add(1, Ordering::SeqCst);
}
}
impl Wakes {
fn waker() -> (Arc<Self>, Waker) {
let wake_count = Arc::new(Self::default());
(Arc::clone(&wake_count), Waker::from(wake_count))
}
fn count(&self) -> usize {
self.0.load(Ordering::SeqCst)
}
}
fn poll(interrupt: &mut Interrupt<Pending<()>>, waker: &Waker) -> Poll<Option<()>> {
Pin::new(interrupt).poll(&mut Context::from_waker(waker))
}
#[test]
fn register_vs_stop() {
loom::model(|| {
let root = Inner::new_root(false);
let mut interrupt = Interrupt::new(&root, pending());
let (wake_count, waker) = Wakes::waker();
let stopper = thread::spawn({
let root = Arc::clone(&root);
move || root.stop()
});
let polled = poll(&mut interrupt, &waker);
stopper.join().unwrap();
if polled.is_pending() {
assert!(
wake_count.count() > 0,
"pending interrupt not woken by stop"
);
}
assert_eq!(poll(&mut interrupt, &waker), Poll::Ready(None));
});
}
#[test]
fn same_waker_repoll_vs_stop() {
loom::model(|| {
let root = Inner::new_root(false);
let mut interrupt = Interrupt::new(&root, pending());
let (wake_count, waker) = Wakes::waker();
assert!(poll(&mut interrupt, &waker).is_pending());
let stopper = thread::spawn({
let root = Arc::clone(&root);
move || root.stop()
});
let polled = poll(&mut interrupt, &waker);
stopper.join().unwrap();
if polled.is_pending() {
assert!(
wake_count.count() > 0,
"pending interrupt not woken by stop"
);
}
assert_eq!(poll(&mut interrupt, &waker), Poll::Ready(None));
});
}
#[test]
fn migration_and_waker_replacement_vs_stop() {
loom::model(|| {
let root = Inner::new_root(false);
let mut interrupt = Interrupt::new(&root, pending());
let (_, first_waker) = Wakes::waker();
assert!(poll(&mut interrupt, &first_waker).is_pending());
let stopper = thread::spawn({
let root = Arc::clone(&root);
move || root.stop()
});
let migrated = thread::spawn(move || {
let (wake_count, waker) = Wakes::waker();
let polled = poll(&mut interrupt, &waker);
(interrupt, wake_count, waker, polled)
});
stopper.join().unwrap();
let (mut interrupt, wake_count, waker, polled) = migrated.join().unwrap();
if polled.is_pending() {
assert!(
wake_count.count() > 0,
"replacement waker not woken by stop"
);
}
assert_eq!(poll(&mut interrupt, &waker), Poll::Ready(None));
});
}
#[test]
fn deregistration_vs_stop() {
loom::model(|| {
let root = Inner::new_root(false);
let mut dropped = Interrupt::new(&root, pending());
let mut survivor = Interrupt::new(&root, pending());
let (_, dropped_waker) = Wakes::waker();
let (wake_count, survivor_waker) = Wakes::waker();
assert!(poll(&mut dropped, &dropped_waker).is_pending());
assert!(poll(&mut survivor, &survivor_waker).is_pending());
let deregistering = thread::spawn(move || drop(dropped));
root.stop();
deregistering.join().unwrap();
assert!(
wake_count.count() > 0,
"surviving interrupt not woken by stop"
);
for shard in root.shards() {
assert_eq!(shard.waiter_count(), 0);
}
assert_eq!(poll(&mut survivor, &survivor_waker), Poll::Ready(None));
});
}
#[test]
fn collection_vs_pending_interrupt() {
loom::model(|| {
let root = Inner::new_root(false);
let child = Inner::new_child(&root);
let mut interrupt = Interrupt::new(&child, pending());
let (wake_count, waker) = Wakes::waker();
let collector = thread::spawn(move || drop(child));
let polled = poll(&mut interrupt, &waker);
collector.join().unwrap();
if polled.is_pending() {
assert!(
wake_count.count() > 0,
"interrupt on collected child not woken"
);
}
assert_eq!(poll(&mut interrupt, &waker), Poll::Ready(None));
assert!(root.shards().iter().all(|shard| !shard.is_stopped()));
});
}
#[test]
fn piggybacked_guard_vs_stop() {
loom::model(|| {
let root = Inner::new_root(false);
let child = Inner::new_child(&root);
let first = thread::spawn({
let child = Arc::clone(&child);
move || Guard::new(&child)
});
let second = Guard::new(&child);
root.stop();
assert!(
!root.is_complete(),
"root complete while a guard created before the stop is alive"
);
drop(first.join().unwrap());
drop(second);
assert!(child.is_complete());
assert!(root.is_complete());
});
}
#[test]
fn last_guard_drop_vs_stop() {
loom::model(|| {
let root = Inner::new_root(false);
let guard = Guard::new(&root);
let dropper = thread::spawn(move || drop(guard));
root.stop();
let listened = root.zero_notifications();
let complete = root.is_complete();
dropper.join().unwrap();
assert!(root.is_complete());
if !complete {
assert!(
root.zero_notifications() > listened,
"completion not notified"
);
}
});
}
#[test]
fn child_guard_drop_vs_root_stop() {
loom::model(|| {
let root = Inner::new_root(false);
let guard = Guard::new(&Inner::new_child(&root));
let dropper = thread::spawn(move || drop(guard));
root.stop();
let listened = root.zero_notifications();
let complete = root.is_complete();
dropper.join().unwrap();
assert!(root.is_complete());
if !complete {
assert!(
root.zero_notifications() > listened,
"completion not notified"
);
}
});
}
#[test]
fn concurrent_first_arrivals() {
loom::model(|| {
let root = Inner::new_root(false);
let child = Inner::new_child(&root);
let other = thread::spawn({
let child = Arc::clone(&child);
move || Guard::new(&child)
});
let guard = Guard::new(&child);
let other = other.join().unwrap();
assert!(!child.is_zero() && !root.is_zero());
assert_eq!(root.guard_count_subtree(), 2);
drop((guard, other));
assert_eq!(root.guard_count_subtree(), 0);
root.stop();
assert!(child.is_complete() && root.is_complete());
for shard in root.shards() {
assert_eq!(shard.state(), STOPPED, "flushed and empty");
}
});
}
#[test]
fn arrival_vs_flush() {
loom::model(|| {
let root = Inner::new_root(false);
let child = Inner::new_child(&root);
drop(Guard::new(&child));
let creator = thread::spawn({
let child = Arc::clone(&child);
move || Guard::new(&child)
});
child.stop();
let guard = creator.join().unwrap();
assert!(!child.is_zero() && !root.is_zero());
drop(guard);
assert!(child.is_complete());
root.stop();
assert!(root.is_complete());
});
}
#[test]
fn collection_vs_root_stop() {
loom::model(|| {
let root = Inner::new_root(false);
let child = Inner::new_child(&root);
drop(Guard::new(&child));
let collector = thread::spawn(move || drop(child));
root.stop();
let listened = root.zero_notifications();
let complete = root.is_complete();
collector.join().unwrap();
assert!(root.is_complete());
if !complete {
assert!(
root.zero_notifications() > listened,
"completion not notified"
);
}
});
}
#[test]
fn child_creation_vs_parent_stop() {
loom::model(|| {
let root = Inner::new_root(false);
let creator = thread::spawn({
let root = Arc::clone(&root);
move || Inner::new_child(&root)
});
root.stop();
let child = creator.join().unwrap();
assert!(child.is_stopped());
});
}
#[test]
fn sibling_creation_vs_collection() {
loom::model(|| {
let root = Inner::new_root(false);
let doomed = Inner::new_child(&root);
drop(Guard::new(&doomed));
let collector = thread::spawn(move || drop(doomed));
let sibling = Inner::new_child(&root);
collector.join().unwrap();
root.stop();
assert!(sibling.is_stopped());
assert!(root.is_complete());
});
}