escouade_plugin_kit/
time.rs1use std::fmt;
5use std::future::Future;
6use std::time::Duration;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub struct Elapsed;
11
12impl fmt::Display for Elapsed {
13 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
14 f.write_str("timed out")
15 }
16}
17
18impl std::error::Error for Elapsed {}
19
20#[cfg(not(target_family = "wasm"))]
22pub async fn sleep(delay: Duration) {
23 tokio::time::sleep(delay).await;
24}
25
26#[cfg(not(target_family = "wasm"))]
28pub async fn timeout<F: Future>(limit: Duration, work: F) -> Result<F::Output, Elapsed> {
29 tokio::time::timeout(limit, work).await.map_err(|_| Elapsed)
30}
31
32#[cfg(target_family = "wasm")]
34pub async fn sleep(delay: Duration) {
35 if let Some(cx) = crate::serve::reactor::cx() {
36 sleep_on(&cx, delay).await;
37 }
38}
39
40#[cfg(target_family = "wasm")]
42pub async fn timeout<F: Future>(limit: Duration, work: F) -> Result<F::Output, Elapsed> {
43 match crate::serve::reactor::cx() {
44 Some(cx) => timeout_on(&cx, limit, work).await,
45 None => Ok(work.await),
46 }
47}
48
49#[cfg(any(test, target_family = "wasm"))]
50pub(crate) use module::{sleep_on, timeout_on};
51
52#[cfg(any(test, target_family = "wasm"))]
53mod module {
54 use super::*;
55 use crate::serve::Cx;
56 use serde_json::{json, Value};
57 use std::future::poll_fn;
58 use std::pin::pin;
59 use std::task::Poll;
60
61 struct Ringing<'a> {
63 cx: &'a Cx,
64 timer: Value,
65 rung: bool,
66 }
67
68 impl Drop for Ringing<'_> {
69 fn drop(&mut self) {
70 if !self.rung {
71 self.cx
72 .notify("timer/cancel", json!({ "timer": self.timer }));
73 }
74 }
75 }
76
77 pub(crate) async fn sleep_on(cx: &Cx, delay: Duration) {
80 let ms = delay.as_millis().min(u128::from(u64::MAX)) as u64;
81 let timer = match cx.request("timer/set", json!({ "ms": ms })).await {
82 Ok(v) => v.get("timer").cloned(),
83 Err(e) => {
84 cx.log("warn", &format!("no timer: {}", e.message));
85 return;
86 }
87 };
88 let Some(timer) = timer else { return };
89 let key = timer.to_string();
90 let mut ringing = Ringing {
91 cx,
92 timer,
93 rung: false,
94 };
95 poll_fn(|_| {
96 if cx.rang(&key) {
97 ringing.rung = true;
98 Poll::Ready(())
99 } else {
100 Poll::Pending
101 }
102 })
103 .await;
104 }
105
106 pub(crate) async fn timeout_on<F: Future>(
108 cx: &Cx,
109 limit: Duration,
110 work: F,
111 ) -> Result<F::Output, Elapsed> {
112 let mut work = pin!(work);
113 let mut limit = pin!(sleep_on(cx, limit));
114 poll_fn(|task| {
115 if let Poll::Ready(done) = work.as_mut().poll(task) {
116 return Poll::Ready(Ok(done));
117 }
118 limit.as_mut().poll(task).map(|()| Err(Elapsed))
119 })
120 .await
121 }
122}
123
124#[cfg(all(test, not(target_family = "wasm")))]
125mod tests {
126 use super::*;
127
128 #[tokio::test(start_paused = true)]
129 async fn a_timeout_lets_through_what_ends_in_time_and_stops_the_rest() {
130 assert_eq!(timeout(Duration::from_secs(1), async { 7 }).await, Ok(7));
131 let slow = sleep(Duration::from_secs(5));
132 assert_eq!(timeout(Duration::from_secs(1), slow).await, Err(Elapsed));
133 }
134}