Skip to main content

bitwarden_threading/
time.rs

1use std::time::Duration;
2
3#[cfg(not(target_arch = "wasm32"))]
4pub async fn sleep(duration: Duration) {
5    tokio::time::sleep(duration).await;
6}
7
8#[cfg(target_arch = "wasm32")]
9pub async fn sleep(duration: Duration) {
10    use gloo_timers::future::sleep;
11
12    sleep(duration).await;
13}
14
15/// Returned by [`timeout`] when the wrapped future did not complete in time.
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub struct ElapsedError;
18
19impl std::fmt::Display for ElapsedError {
20    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21        f.write_str("operation timed out")
22    }
23}
24
25impl std::error::Error for ElapsedError {}
26
27#[cfg(not(target_arch = "wasm32"))]
28pub async fn timeout<F: std::future::Future>(
29    duration: Duration,
30    future: F,
31) -> Result<F::Output, ElapsedError> {
32    tokio::time::timeout(duration, future)
33        .await
34        .map_err(|_| ElapsedError)
35}
36
37#[cfg(target_arch = "wasm32")]
38pub async fn timeout<F: std::future::Future>(
39    duration: Duration,
40    future: F,
41) -> Result<F::Output, ElapsedError> {
42    let (tx, rx) = tokio::sync::oneshot::channel();
43    wasm_bindgen_futures::spawn_local(async move {
44        sleep(duration).await;
45        let _ = tx.send(());
46    });
47
48    tokio::pin!(future);
49    tokio::select! {
50        result = &mut future => Ok(result),
51        _ = rx => Err(ElapsedError),
52    }
53}
54
55#[cfg(test)]
56mod test {
57    use wasm_bindgen_test::wasm_bindgen_test;
58
59    #[wasm_bindgen_test]
60    #[allow(dead_code)] // Not actually dead, but rust-analyzer doesn't understand `wasm_bindgen_test`
61    async fn should_sleep_wasm() {
62        use js_sys::Date;
63
64        use super::*;
65
66        console_error_panic_hook::set_once();
67        let start = Date::now();
68
69        sleep(Duration::from_millis(100)).await;
70
71        let end = Date::now();
72        let elapsed = end - start;
73
74        assert!(elapsed >= 90.0, "Elapsed time was less than expected");
75    }
76
77    #[tokio::test]
78    async fn should_sleep_tokio() {
79        use std::time::Instant;
80
81        use super::*;
82
83        let start = Instant::now();
84
85        sleep(Duration::from_millis(100)).await;
86
87        let end = Instant::now();
88        let elapsed = end.duration_since(start);
89
90        assert!(
91            elapsed >= Duration::from_millis(90),
92            "Elapsed time was less than expected"
93        );
94    }
95
96    #[tokio::test]
97    async fn timeout_returns_value_when_future_completes_first() {
98        use std::time::Duration;
99
100        use super::timeout;
101
102        let result = timeout(Duration::from_secs(5), async { 42 }).await;
103        assert_eq!(result, Ok(42));
104    }
105
106    #[tokio::test(flavor = "current_thread", start_paused = true)]
107    async fn timeout_returns_elapsed_when_sleep_first() {
108        use std::time::Duration;
109
110        use super::{ElapsedError, timeout};
111
112        let result = timeout(
113            Duration::from_millis(10),
114            tokio::time::sleep(Duration::from_secs(60)),
115        )
116        .await;
117        assert_eq!(result, Err(ElapsedError));
118    }
119}