Skip to main content

origincloud_core/
runtime.rs

1//! What differs between a native target and a browser
2//! (`wasm32-unknown-unknown`): whether futures must be `Send`, how to wait,
3//! how to lock across a wait, and what time it is.
4//!
5//! In a browser, reqwest's futures hold JavaScript promises
6//! (`Rc<RefCell<..>>`), which are not `Send`, and there is one thread, so
7//! nothing needs them to be. Natively the bounds stay as they were, so a
8//! client can be moved between Tokio's worker threads.
9
10use std::future::Future;
11use std::pin::Pin;
12use std::time::Duration;
13#[cfg(any(not(target_arch = "wasm32"), feature = "service-account-wasm"))]
14use std::time::SystemTime;
15
16use futures_core::Stream;
17
18#[cfg(not(target_arch = "wasm32"))]
19mod bounds {
20    /// `Send` natively; nothing in a browser, where futures holding
21    /// JavaScript values aren't `Send`. The bound the SDK puts on the
22    /// futures and values it boxes.
23    pub trait MaybeSend: Send {}
24    impl<T: Send + ?Sized> MaybeSend for T {}
25}
26
27#[cfg(target_arch = "wasm32")]
28mod bounds {
29    /// `Send` natively; nothing in a browser, where futures holding
30    /// JavaScript values aren't `Send`. The bound the SDK puts on the
31    /// futures and values it boxes.
32    pub trait MaybeSend {}
33    impl<T: ?Sized> MaybeSend for T {}
34}
35
36pub use bounds::MaybeSend;
37
38/// A boxed future: `Send` natively, not in a browser. (Named apart from
39/// `futures::future::BoxFuture`, which is always `Send`.)
40#[cfg(not(target_arch = "wasm32"))]
41pub type MaybeSendFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
42/// A boxed future: `Send` natively, not in a browser. (Named apart from
43/// `futures::future::BoxFuture`, which is always `Send`.)
44#[cfg(target_arch = "wasm32")]
45pub type MaybeSendFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
46
47/// A boxed stream: `Send` natively, not in a browser.
48#[cfg(not(target_arch = "wasm32"))]
49pub(crate) type BoxStream<T> = Pin<Box<dyn Stream<Item = T> + Send>>;
50/// A boxed stream: `Send` natively, not in a browser.
51#[cfg(target_arch = "wasm32")]
52pub(crate) type BoxStream<T> = Pin<Box<dyn Stream<Item = T>>>;
53
54/// A lock held across an `.await`: Tokio's natively, as before; in a
55/// browser, where Tokio isn't a dependency, `futures-util`'s.
56#[cfg(all(target_arch = "wasm32", feature = "service-account-wasm"))]
57pub(crate) use futures_util::lock::Mutex;
58#[cfg(not(target_arch = "wasm32"))]
59pub(crate) use tokio::sync::Mutex;
60
61/// The longest a browser timer waits: `setTimeout` takes a signed 32-bit
62/// number of milliseconds, and fires at once for a longer one.
63const MAX_BROWSER_TIMER: Duration = Duration::from_millis(i32::MAX as u64);
64
65/// Waits `delay`: on Tokio's timer natively; on the browser's
66/// (`setTimeout`) in a browser, which has no Tokio timer driver, so
67/// `tokio::time::sleep` would panic there. A browser waits at most
68/// [`MAX_BROWSER_TIMER`] (about 24.8 days), as a longer delay, such as a
69/// server's retry delay, would otherwise fire at once or panic.
70pub(crate) async fn sleep(delay: Duration) {
71    #[cfg(not(target_arch = "wasm32"))]
72    tokio::time::sleep(delay).await;
73    #[cfg(target_arch = "wasm32")]
74    gloo_timers::future::sleep(browser_timer(delay)).await;
75}
76
77/// `delay`, at most as long as a browser timer can wait.
78#[cfg_attr(not(target_arch = "wasm32"), allow(dead_code))]
79fn browser_timer(delay: Duration) -> Duration {
80    delay.min(MAX_BROWSER_TIMER)
81}
82
83/// The current time. In a browser `SystemTime::now` panics (the standard
84/// library has no clock on `wasm32-unknown-unknown`), so it is read from
85/// JavaScript's `Date.now()` there, in milliseconds since the Unix epoch.
86#[cfg(any(not(target_arch = "wasm32"), feature = "service-account-wasm"))]
87pub(crate) fn now() -> SystemTime {
88    #[cfg(not(target_arch = "wasm32"))]
89    {
90        SystemTime::now()
91    }
92    #[cfg(target_arch = "wasm32")]
93    {
94        // A whole, non-negative number of milliseconds; `as` saturates.
95        let millis = js_sys::Date::now() as u64;
96        SystemTime::UNIX_EPOCH + Duration::from_millis(millis)
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    #[test]
105    fn a_browser_timer_waits_at_most_what_set_timeout_takes() {
106        let max = Duration::from_millis(2_147_483_647);
107        assert_eq!(
108            browser_timer(Duration::from_millis(200)),
109            Duration::from_millis(200)
110        );
111        assert_eq!(browser_timer(max), max);
112        assert_eq!(browser_timer(max + Duration::from_millis(1)), max);
113        assert_eq!(
114            browser_timer(Duration::from_secs(10 * 365 * 24 * 60 * 60)),
115            max
116        );
117        assert_eq!(browser_timer(Duration::MAX), max);
118    }
119}