Skip to main content

trillium_server_common/runtime/
runtime_trait.rs

1use super::{DroppableFuture, Runtime};
2use futures_lite::Stream;
3use std::{
4    future::Future,
5    pin::Pin,
6    task::{Context, Poll},
7    time::Duration,
8};
9
10/// A trait that covers async runtime behavior.
11///
12/// You likely do not need to name this type. For a type-erased runtime, see [`Runtime`]
13pub trait RuntimeTrait: Into<Runtime> + Clone + Send + Sync + 'static {
14    /// Spawn a future on the runtime, returning a future that has detach-on-drop semantics
15    ///
16    /// As the various runtimes each has different behavior for spawn, implementations of this trait
17    /// are expected to conform to the following:
18    ///
19    /// * detach on drop: If the returned [`DroppableFuture`] is dropped immediately, the task will
20    ///   continue to execute until completion.
21    ///
22    /// * unwinding: If the spawned future panics, this must not propagate to the join handle.
23    ///   Instead, the awaiting the join handle returns None in case of panic.
24    fn spawn<Fut>(
25        &self,
26        fut: Fut,
27    ) -> DroppableFuture<impl Future<Output = Option<Fut::Output>> + Send + 'static>
28    where
29        Fut: Future + Send + 'static,
30        Fut::Output: Send + 'static;
31
32    /// Spawn a future on the runtime without a join handle.
33    ///
34    /// The output type is `()` because no completion signal or output is available to the
35    /// caller. If the spawned future panics, the panic does not propagate to the spawning
36    /// task; whether it is logged or swallowed is runtime-specific.
37    ///
38    /// The default implementation spawns and immediately drops the join handle, relying on
39    /// the detach-on-drop contract of [`spawn`][Self::spawn].
40    fn spawn_detached<Fut>(&self, fut: Fut)
41    where
42        Fut: Future<Output = ()> + Send + 'static,
43    {
44        drop(self.spawn(fut));
45    }
46
47    /// Wake in this amount of wall time
48    fn delay(&self, duration: Duration) -> impl Future<Output = ()> + Send;
49
50    /// Returns a [`Stream`] that yields a `()` on the provided period
51    fn interval(&self, period: Duration) -> impl Stream<Item = ()> + Send + 'static;
52
53    /// Runtime implementation hook for blocking on a top level future.
54    fn block_on<Fut>(&self, fut: Fut) -> Fut::Output
55    where
56        Fut: Future;
57
58    /// Race a future against the provided duration, returning None in case of timeout.
59    fn timeout<'runtime, 'fut, Fut>(
60        &'runtime self,
61        duration: Duration,
62        fut: Fut,
63    ) -> impl Future<Output = Option<Fut::Output>> + Send + 'fut
64    where
65        Fut: Future + Send + 'fut,
66        Fut::Output: Send + 'static,
67        'runtime: 'fut,
68    {
69        Timeout {
70            fut,
71            delay: self.delay(duration),
72        }
73    }
74
75    /// trap and return a [`Stream`] of signals that match the provided signals
76    fn hook_signals(
77        &self,
78        signals: impl IntoIterator<Item = i32>,
79    ) -> impl Stream<Item = i32> + Send + 'static {
80        let _ = signals;
81        futures_lite::stream::empty()
82    }
83}
84
85pin_project_lite::pin_project! {
86    /// A hand-written combinator rather than racing `async` blocks because a generator that
87    /// captures `fut` and awaits it stores the future twice (capture slot + await slot),
88    /// doubling the caller's storage for the timed-out future.
89    struct Timeout<Fut, Delay> {
90        #[pin]
91        fut: Fut,
92        #[pin]
93        delay: Delay,
94    }
95}
96
97impl<Fut: Future, Delay: Future<Output = ()>> Future for Timeout<Fut, Delay> {
98    type Output = Option<Fut::Output>;
99
100    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
101        let this = self.project();
102        if let Poll::Ready(output) = this.fut.poll(cx) {
103            return Poll::Ready(Some(output));
104        }
105        this.delay.poll(cx).map(|()| None)
106    }
107}