Skip to main content

trillium_testing/runtimeless/
runtime.rs

1use futures_lite::{Stream, future};
2use std::{future::Future, sync::Arc, thread, time::Duration};
3use trillium_server_common::{DroppableFuture, Runtime, RuntimeTrait};
4
5/// a runtime that isn't a runtime
6#[derive(Debug, Clone, Copy, Default)]
7pub struct RuntimelessRuntime(());
8impl RuntimeTrait for RuntimelessRuntime {
9    fn spawn<Fut>(
10        &self,
11        fut: Fut,
12    ) -> DroppableFuture<impl Future<Output = Option<Fut::Output>> + Send + 'static>
13    where
14        Fut: Future + Send + 'static,
15        Fut::Output: Send + 'static,
16    {
17        let rt = *self;
18        let (send, receive) = async_channel::bounded(1);
19        thread::spawn(move || {
20            let _ = send.send_blocking(rt.block_on(fut));
21        });
22        DroppableFuture::new(async move { receive.recv().await.ok() })
23    }
24
25    async fn delay(&self, duration: Duration) {
26        let (send, receive) = async_channel::bounded(1);
27        thread::spawn(move || {
28            thread::sleep(duration);
29            let _ = send.send_blocking(());
30        });
31        let _ = receive.recv().await;
32    }
33
34    fn interval(&self, period: Duration) -> impl Stream<Item = ()> + Send + 'static {
35        let (send, receive) = async_channel::bounded(1);
36        thread::spawn(move || {
37            loop {
38                thread::sleep(period);
39                if send.send_blocking(()).is_err() {
40                    break;
41                }
42            }
43        });
44
45        receive
46    }
47
48    fn block_on<Fut: Future>(&self, fut: Fut) -> Fut::Output {
49        future::block_on(fut)
50    }
51}
52
53impl From<RuntimelessRuntime> for Runtime {
54    fn from(value: RuntimelessRuntime) -> Self {
55        Arc::new(value).into()
56    }
57}
58
59impl RuntimelessRuntime {
60    /// Spawn a future on the runtime, returning a future that has detach-on-drop semantics
61    ///
62    /// Spawned tasks conform to the following behavior:
63    ///
64    /// * detach on drop: If the returned [`DroppableFuture`] is dropped immediately, the task will
65    ///   continue to execute until completion.
66    ///
67    /// * unwinding: If the spawned future panics, this must not propagate to the join handle.
68    ///   Instead, the awaiting the join handle returns None in case of panic.
69    pub fn spawn<Fut>(
70        &self,
71        fut: Fut,
72    ) -> DroppableFuture<impl Future<Output = Option<Fut::Output>> + Send + 'static + use<Fut>>
73    where
74        Fut: Future + Send + 'static,
75        Fut::Output: Send + 'static,
76    {
77        let rt = *self;
78        let (send, receive) = async_channel::bounded(1);
79        thread::spawn(move || {
80            let _ = send.send_blocking(rt.block_on(fut));
81        });
82        DroppableFuture::new(async move { receive.recv().await.ok() })
83    }
84
85    /// Wake in this amount of wall time
86    pub async fn delay(&self, duration: Duration) {
87        RuntimeTrait::delay(self, duration).await
88    }
89
90    /// Returns a [`Stream`] that yields a `()` on the provided period
91    pub fn interval(&self, period: Duration) -> impl Stream<Item = ()> + Send + 'static + use<> {
92        let (send, receive) = async_channel::bounded(1);
93        thread::spawn(move || {
94            loop {
95                thread::sleep(period);
96                if send.is_closed() {
97                    break;
98                }
99                let _ = send.send_blocking(());
100            }
101        });
102
103        receive
104    }
105
106    /// Runtime implementation hook for blocking on a top level future.
107    pub fn block_on<Fut: Future>(&self, fut: Fut) -> Fut::Output {
108        future::block_on(fut)
109    }
110
111    /// Race a future against the provided duration, returning None in case of timeout.
112    pub async fn timeout<Fut>(&self, duration: Duration, fut: Fut) -> Option<Fut::Output>
113    where
114        Fut: Future + Send,
115        Fut::Output: Send + 'static,
116    {
117        RuntimeTrait::timeout(self, duration, fut).await
118    }
119}