Skip to main content

rusty_harness/
runtime.rs

1//! The runtime seam.
2//!
3//! The core crate never names a concrete executor in a public signature. Code that has
4//! to spawn a task or wait on a timer takes a [`Runtime`], and the caller picks an
5//! implementation: [`TokioRuntime`] (feature `rt-tokio`) on native targets, or
6//! `WasmRuntime` (feature `rt-wasm`) on `wasm32`.
7
8use core::future::Future;
9use core::time::Duration;
10
11/// `Send` on native targets, nothing on `wasm32`.
12///
13/// Browser futures (JS promises, timers) are `!Send`, and `wasm32-unknown-unknown` is
14/// single-threaded, so bounds use `MaybeSend` instead of `Send` to stay satisfiable there.
15#[cfg(not(target_arch = "wasm32"))]
16pub trait MaybeSend: Send {}
17
18#[cfg(not(target_arch = "wasm32"))]
19impl<T: Send + ?Sized> MaybeSend for T {}
20
21/// `Send` on native targets, nothing on `wasm32`.
22///
23/// Browser futures (JS promises, timers) are `!Send`, and `wasm32-unknown-unknown` is
24/// single-threaded, so bounds use `MaybeSend` instead of `Send` to stay satisfiable there.
25#[cfg(target_arch = "wasm32")]
26pub trait MaybeSend {}
27
28#[cfg(target_arch = "wasm32")]
29impl<T: ?Sized> MaybeSend for T {}
30
31/// The capabilities the harness needs from an async runtime.
32pub trait Runtime {
33    /// Runs `future` in the background, detached from the caller.
34    fn spawn<F>(&self, future: F)
35    where
36        F: Future<Output = ()> + MaybeSend + 'static;
37
38    /// Completes after `duration` has elapsed.
39    fn sleep(&self, duration: Duration) -> impl Future<Output = ()> + MaybeSend;
40}
41
42#[cfg(feature = "rt-tokio")]
43pub use self::tokio::TokioRuntime;
44
45#[cfg(all(feature = "rt-wasm", target_arch = "wasm32"))]
46pub use self::wasm::WasmRuntime;
47
48#[cfg(feature = "rt-tokio")]
49mod tokio {
50    use core::future::Future;
51    use core::time::Duration;
52
53    use super::{MaybeSend, Runtime};
54
55    /// [`Runtime`] backed by a tokio runtime handle.
56    #[derive(Clone, Debug)]
57    pub struct TokioRuntime {
58        handle: ::tokio::runtime::Handle,
59    }
60
61    impl TokioRuntime {
62        /// Uses the tokio runtime the caller is running on.
63        ///
64        /// # Panics
65        ///
66        /// Panics when called outside a tokio runtime.
67        #[must_use]
68        pub fn current() -> Self {
69            Self::from_handle(::tokio::runtime::Handle::current())
70        }
71
72        /// Uses the tokio runtime behind `handle`.
73        #[must_use]
74        pub fn from_handle(handle: ::tokio::runtime::Handle) -> Self {
75            Self { handle }
76        }
77    }
78
79    impl Runtime for TokioRuntime {
80        fn spawn<F>(&self, future: F)
81        where
82            F: Future<Output = ()> + MaybeSend + 'static,
83        {
84            drop(self.handle.spawn(future));
85        }
86
87        fn sleep(&self, duration: Duration) -> impl Future<Output = ()> + MaybeSend {
88            let _guard = self.handle.enter();
89            ::tokio::time::sleep(duration)
90        }
91    }
92
93    #[cfg(test)]
94    mod tests {
95        use core::time::Duration;
96
97        use super::{Runtime, TokioRuntime};
98
99        #[tokio::test]
100        async fn spawn_runs_the_future() {
101            let (tx, rx) = ::tokio::sync::oneshot::channel();
102            TokioRuntime::current().spawn(async move {
103                tx.send(42).expect("receiver alive");
104            });
105            assert_eq!(rx.await.expect("task ran"), 42);
106        }
107
108        #[tokio::test(start_paused = true)]
109        async fn sleep_waits_for_the_duration() {
110            let start = ::tokio::time::Instant::now();
111            TokioRuntime::current().sleep(Duration::from_secs(5)).await;
112            assert!(start.elapsed() >= Duration::from_secs(5));
113        }
114    }
115}
116
117#[cfg(all(feature = "rt-wasm", target_arch = "wasm32"))]
118mod wasm {
119    use core::future::Future;
120    use core::time::Duration;
121
122    use super::{MaybeSend, Runtime};
123
124    /// [`Runtime`] backed by the JS event loop: `spawn_local` and `setTimeout`.
125    #[derive(Clone, Copy, Debug, Default)]
126    pub struct WasmRuntime;
127
128    impl Runtime for WasmRuntime {
129        fn spawn<F>(&self, future: F)
130        where
131            F: Future<Output = ()> + MaybeSend + 'static,
132        {
133            wasm_bindgen_futures::spawn_local(future);
134        }
135
136        fn sleep(&self, duration: Duration) -> impl Future<Output = ()> + MaybeSend {
137            gloo_timers::future::sleep(duration)
138        }
139    }
140}