Skip to main content

iota_sdk_graphql_client/
wait.rs

1// Copyright (c) 2026 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4//! Async `sleep` / `timeout`. Tokio's time driver isn't available on wasm32,
5//! so on that target we drive a cancelable `setTimeout`-backed future
6//! instead.
7
8use std::{future::Future, time::Duration};
9
10/// Sleep for the given duration.
11#[cfg(not(target_arch = "wasm32"))]
12pub(crate) async fn sleep(duration: Duration) {
13    tokio::time::sleep(duration).await;
14}
15
16#[cfg(target_arch = "wasm32")]
17pub(crate) async fn sleep(duration: Duration) {
18    wasm_time::Sleep::new(duration).await;
19}
20
21/// Race `f` against a timer. Returns `Err(Elapsed)` if the timer fires first.
22#[cfg(not(target_arch = "wasm32"))]
23pub(crate) async fn timeout<F: Future>(duration: Duration, f: F) -> Result<F::Output, Elapsed> {
24    tokio::time::timeout(duration, f).await.map_err(|_| Elapsed)
25}
26
27#[cfg(target_arch = "wasm32")]
28pub(crate) async fn timeout<F: Future>(duration: Duration, f: F) -> Result<F::Output, Elapsed> {
29    use futures::future::Either;
30    let work = std::pin::pin!(f);
31    let timer = std::pin::pin!(wasm_time::Sleep::new(duration));
32    match futures::future::select(work, timer).await {
33        Either::Left((v, _)) => Ok(v),
34        Either::Right((_, _)) => Err(Elapsed),
35    }
36}
37
38/// Marker type returned by [`timeout`] when the deadline elapses.
39#[derive(Debug)]
40pub(crate) struct Elapsed;
41
42impl std::fmt::Display for Elapsed {
43    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44        f.write_str("deadline elapsed")
45    }
46}
47
48impl std::error::Error for Elapsed {}
49
50#[cfg(target_arch = "wasm32")]
51mod wasm_time {
52    use std::{
53        cell::Cell,
54        future::Future,
55        pin::Pin,
56        rc::Rc,
57        task::{Context, Poll, Waker},
58        time::Duration,
59    };
60
61    use wasm_bindgen::{closure::Closure, prelude::*};
62
63    #[wasm_bindgen]
64    unsafe extern "C" {
65        #[wasm_bindgen(js_name = setTimeout)]
66        fn set_timeout(callback: &js_sys::Function, ms: i32) -> JsValue;
67        #[wasm_bindgen(js_name = clearTimeout)]
68        fn clear_timeout(handle: &JsValue);
69    }
70
71    struct State {
72        fired: Cell<bool>,
73        waker: Cell<Option<Waker>>,
74    }
75
76    /// Cancelable sleep — `clearTimeout` on drop so Node's event loop
77    /// doesn't stay alive past a future that's been raced out.
78    pub(super) struct Sleep {
79        state: Rc<State>,
80        handle: Option<JsValue>,
81        _closure: Option<Closure<dyn FnMut()>>,
82    }
83
84    impl Sleep {
85        pub(super) fn new(duration: Duration) -> Self {
86            let state = Rc::new(State {
87                fired: Cell::new(false),
88                waker: Cell::new(None),
89            });
90            let cb_state = state.clone();
91            let closure: Closure<dyn FnMut()> = Closure::new(move || {
92                cb_state.fired.set(true);
93                if let Some(w) = cb_state.waker.take() {
94                    w.wake();
95                }
96            });
97            let ms = duration.as_millis().min(i32::MAX as u128) as i32;
98            let handle = set_timeout(closure.as_ref().unchecked_ref(), ms);
99            Self {
100                state,
101                handle: Some(handle),
102                _closure: Some(closure),
103            }
104        }
105    }
106
107    impl Future for Sleep {
108        type Output = ();
109
110        fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
111            if self.state.fired.get() {
112                self.handle = None;
113                Poll::Ready(())
114            } else {
115                self.state.waker.set(Some(cx.waker().clone()));
116                Poll::Pending
117            }
118        }
119    }
120
121    impl Drop for Sleep {
122        fn drop(&mut self) {
123            if let Some(handle) = self.handle.take() {
124                clear_timeout(&handle);
125            }
126        }
127    }
128}