use std::fmt;
use std::future::Future;
use std::time::Duration;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Elapsed;
impl fmt::Display for Elapsed {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("timed out")
}
}
impl std::error::Error for Elapsed {}
#[cfg(not(target_family = "wasm"))]
pub async fn sleep(delay: Duration) {
tokio::time::sleep(delay).await;
}
#[cfg(not(target_family = "wasm"))]
pub async fn timeout<F: Future>(limit: Duration, work: F) -> Result<F::Output, Elapsed> {
tokio::time::timeout(limit, work).await.map_err(|_| Elapsed)
}
#[cfg(target_family = "wasm")]
pub async fn sleep(delay: Duration) {
if let Some(cx) = crate::serve::reactor::cx() {
sleep_on(&cx, delay).await;
}
}
#[cfg(target_family = "wasm")]
pub async fn timeout<F: Future>(limit: Duration, work: F) -> Result<F::Output, Elapsed> {
match crate::serve::reactor::cx() {
Some(cx) => timeout_on(&cx, limit, work).await,
None => Ok(work.await),
}
}
#[cfg(any(test, target_family = "wasm"))]
pub(crate) use module::{sleep_on, timeout_on};
#[cfg(any(test, target_family = "wasm"))]
mod module {
use super::*;
use crate::serve::Cx;
use serde_json::{json, Value};
use std::future::poll_fn;
use std::pin::pin;
use std::task::Poll;
struct Ringing<'a> {
cx: &'a Cx,
timer: Value,
rung: bool,
}
impl Drop for Ringing<'_> {
fn drop(&mut self) {
if !self.rung {
self.cx
.notify("timer/cancel", json!({ "timer": self.timer }));
}
}
}
pub(crate) async fn sleep_on(cx: &Cx, delay: Duration) {
let ms = delay.as_millis().min(u128::from(u64::MAX)) as u64;
let timer = match cx.request("timer/set", json!({ "ms": ms })).await {
Ok(v) => v.get("timer").cloned(),
Err(e) => {
cx.log("warn", &format!("no timer: {}", e.message));
return;
}
};
let Some(timer) = timer else { return };
let key = timer.to_string();
let mut ringing = Ringing {
cx,
timer,
rung: false,
};
poll_fn(|_| {
if cx.rang(&key) {
ringing.rung = true;
Poll::Ready(())
} else {
Poll::Pending
}
})
.await;
}
pub(crate) async fn timeout_on<F: Future>(
cx: &Cx,
limit: Duration,
work: F,
) -> Result<F::Output, Elapsed> {
let mut work = pin!(work);
let mut limit = pin!(sleep_on(cx, limit));
poll_fn(|task| {
if let Poll::Ready(done) = work.as_mut().poll(task) {
return Poll::Ready(Ok(done));
}
limit.as_mut().poll(task).map(|()| Err(Elapsed))
})
.await
}
}
#[cfg(all(test, not(target_family = "wasm")))]
mod tests {
use super::*;
#[tokio::test(start_paused = true)]
async fn a_timeout_lets_through_what_ends_in_time_and_stops_the_rest() {
assert_eq!(timeout(Duration::from_secs(1), async { 7 }).await, Ok(7));
let slow = sleep(Duration::from_secs(5));
assert_eq!(timeout(Duration::from_secs(1), slow).await, Err(Elapsed));
}
}