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}