use std::future::Future;
use std::pin::{pin, Pin};
use std::sync::mpsc::{self, RecvTimeoutError};
use std::task::Poll;
use std::time::{Duration, Instant};
pub const HANG_CEILING: Duration = Duration::from_secs(45);
pub async fn within<F: Future>(label: &str, fut: F) -> F::Output {
within_limit(label, fut, HANG_CEILING).await
}
async fn within_limit<F: Future>(label: &str, fut: F, ceiling: Duration) -> F::Output {
let started = Instant::now();
let (expired_tx, mut expired_rx) = tokio::sync::oneshot::channel::<()>();
let (_cancel, cancelled) = mpsc::channel::<()>();
std::thread::Builder::new()
.name("harn-hang-ceiling".to_string())
.spawn(move || {
if cancelled.recv_timeout(ceiling) == Err(RecvTimeoutError::Timeout) {
let _ = expired_tx.send(());
}
})
.expect("start hang-ceiling deadline thread");
let mut fut = pin!(fut);
std::future::poll_fn(|cx| {
if let Poll::Ready(output) = fut.as_mut().poll(cx) {
return Poll::Ready(output);
}
assert_ne!(
Pin::new(&mut expired_rx).poll(cx),
Poll::Ready(Ok(())),
"hang: {label} did not complete within the {ceiling:?} test hang ceiling \
(waited {:?})",
started.elapsed()
);
Poll::Pending
})
.await
}
pub fn recv_within<T>(label: &str, rx: &mpsc::Receiver<T>) -> T {
let started = Instant::now();
match rx.recv_timeout(HANG_CEILING) {
Ok(value) => value,
Err(RecvTimeoutError::Timeout) => panic!(
"hang: {label} did not arrive within the {HANG_CEILING:?} test hang ceiling \
(waited {:?})",
started.elapsed()
),
Err(RecvTimeoutError::Disconnected) => panic!(
"{label}: channel closed before a value arrived (waited {:?})",
started.elapsed()
),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn within_returns_the_output_of_a_ready_future() {
assert_eq!(within("ready value", async { 7 }).await, 7);
}
#[test]
#[should_panic(expected = "hang: missing event did not complete within")]
fn within_names_a_hang_without_a_tokio_timer() {
tokio::runtime::Builder::new_current_thread()
.build()
.expect("runtime without a time driver")
.block_on(within_limit(
"missing event",
std::future::pending::<()>(),
Duration::ZERO,
));
}
#[tokio::test(start_paused = true)]
async fn within_ignores_paused_tokio_time() {
let (tx, rx) = tokio::sync::oneshot::channel();
let (go_tx, go_rx) = mpsc::channel::<()>();
let producer = std::thread::spawn(move || {
if go_rx.recv().is_ok() {
let _ = tx.send("done");
}
});
let value = within("event from an OS thread", async {
tokio::time::advance(HANG_CEILING * 4).await;
go_tx.send(()).expect("producer waiting");
rx.await.expect("sender kept")
})
.await;
assert_eq!(value, "done");
producer.join().expect("producer thread");
}
#[test]
fn recv_within_returns_a_sent_value() {
let (tx, rx) = mpsc::channel();
tx.send(3).expect("send");
assert_eq!(recv_within("sent value", &rx), 3);
}
#[test]
#[should_panic(expected = "closed channel: channel closed before a value arrived")]
fn recv_within_names_a_closed_channel() {
let (tx, rx) = mpsc::channel::<()>();
drop(tx);
recv_within("closed channel", &rx);
}
}