use core::future::Future;
use core::pin::Pin;
use core::task::Poll;
use std::sync::{Arc, mpsc};
use std::time::Duration;
use crate::runtime::{Builder, Runtime};
use crate::time;
const SAFETY: Duration = Duration::from_secs(20);
fn paused(workers: usize) -> Runtime {
Builder::new()
.worker_threads(workers)
.pin_workers(false)
.start_paused(true)
.build()
.unwrap()
}
fn eventually(what: &str, mut done: impl FnMut() -> bool) {
let deadline = std::time::Instant::now() + SAFETY;
let mut pause = Duration::from_micros(10);
while !done() {
assert!(
std::time::Instant::now() < deadline,
"{what} never happened"
);
std::thread::sleep(pause);
pause = (pause * 2).min(Duration::from_millis(5));
}
}
fn block_on_within<F>(rt: &Runtime, future: F) -> Result<(), mpsc::RecvTimeoutError>
where
F: Future<Output = ()> + Send + 'static,
{
let handle = rt.handle();
let (done, ended) = mpsc::channel();
std::thread::spawn(move || {
handle.block_on(future);
let _ = done.send(());
});
ended.recv_timeout(SAFETY)
}
#[test]
fn a_timer_removed_off_its_parked_shard_lets_the_clock_jump() {
let rt = paused(1);
let inner = Arc::clone(rt.inner());
let start = inner.clock.now_nanos();
let (out, armed) = mpsc::channel();
rt.spawn(async move {
let mut sleep = time::sleep(Duration::from_secs(3600));
let pending =
core::future::poll_fn(|cx| Poll::Ready(Pin::new(&mut sleep).poll(cx).is_pending()))
.await;
out.send((pending, sleep)).unwrap();
})
.detach();
let (pending, sleep) = armed.recv_timeout(SAFETY).unwrap();
assert!(pending, "the sleep was armed on the worker");
let worker = Arc::clone(inner.workers.shards().next().unwrap());
eventually("the worker parked for good past the hour", || {
inner.clock.now_nanos() >= start + 3_600_000_000_000
&& worker.timer_at() == u64::MAX
&& worker.idle()
});
drop(sleep);
assert!(
block_on_within(&rt, async { time::sleep(Duration::from_secs(1)).await }).is_ok(),
"the clock never jumped to the next sleep: the worker sleeps on, holding the removal"
);
}
struct Stamp {
inner: Arc<crate::rt::runtime::Inner>,
at: std::sync::Mutex<Option<std::time::Instant>>,
}
impl std::task::Wake for Stamp {
fn wake(self: Arc<Self>) {
self.wake_by_ref();
}
fn wake_by_ref(self: &Arc<Self>) {
let now = self.inner.clock.now();
*self.at.lock().unwrap() = Some(now);
}
}
#[test]
fn a_foreign_arm_is_never_jumped_past() {
let rt = paused(1);
rt.spawn(async {
loop {
time::sleep(Duration::from_secs(10 * 3600)).await;
}
})
.detach();
let _inside = rt.enter();
let stamp = Arc::new(Stamp {
inner: Arc::clone(rt.inner()),
at: std::sync::Mutex::new(None),
});
let waker = core::task::Waker::from(Arc::clone(&stamp));
let mut cx = core::task::Context::from_waker(&waker);
let mut armed = 0;
for round in 0..400 {
let mut sleep = time::sleep(Duration::from_millis(1));
let deadline = sleep.deadline();
*stamp.at.lock().unwrap() = None;
if Pin::new(&mut sleep).poll(&mut cx).is_ready() {
continue;
}
armed += 1;
let mut woken_at = None;
eventually(&format!("the foreign sleep woken in round {round}"), || {
woken_at = *stamp.at.lock().unwrap();
woken_at.is_some()
});
let at = time::Instant::from_std(woken_at.unwrap());
assert!(
at <= deadline + Duration::from_millis(1),
"round {round}: the clock jumped past the foreign sleep"
);
assert!(Pin::new(&mut sleep).poll(&mut cx).is_ready());
}
assert!(armed > 0, "every sleep ended before its first poll");
}