Skip to main content

trillium_smol/
runtime.rs

1use async_io::Timer;
2use async_task::Task;
3use futures_lite::{FutureExt, Stream, StreamExt};
4use std::{
5    future::Future,
6    pin::Pin,
7    sync::Arc,
8    task::{Context, Poll},
9    time::Duration,
10};
11use trillium_server_common::{DroppableFuture, Runtime, RuntimeTrait};
12
13/// Runtime for Smol
14#[derive(Debug, Clone, Copy, Default)]
15pub struct SmolRuntime(());
16
17struct DetachOnDrop<Output>(Option<Task<Output>>);
18impl<Output> Future for DetachOnDrop<Output> {
19    type Output = Output;
20
21    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
22        Pin::new(self.0.as_mut().unwrap()).poll(cx)
23    }
24}
25
26impl<Output> Drop for DetachOnDrop<Output> {
27    fn drop(&mut self) {
28        if let Some(task) = self.0.take() {
29            task.detach();
30        }
31    }
32}
33
34impl RuntimeTrait for SmolRuntime {
35    fn spawn<Fut>(
36        &self,
37        fut: Fut,
38    ) -> DroppableFuture<impl Future<Output = Option<Fut::Output>> + Send + 'static>
39    where
40        Fut: Future + Send + 'static,
41        Fut::Output: Send + 'static,
42    {
43        let join_handle = DetachOnDrop(Some(async_global_executor::spawn(fut)));
44        DroppableFuture::new(async move { join_handle.catch_unwind().await.ok() })
45    }
46
47    async fn delay(&self, duration: Duration) {
48        Timer::after(duration).await;
49    }
50
51    fn interval(&self, period: Duration) -> impl Stream<Item = ()> + Send + 'static {
52        Timer::interval(period).map(|_| ())
53    }
54
55    fn block_on<Fut: Future>(&self, fut: Fut) -> Fut::Output {
56        async_global_executor::block_on(fut)
57    }
58
59    #[cfg(unix)]
60    fn hook_signals(
61        &self,
62        signals: impl IntoIterator<Item = i32>,
63    ) -> impl Stream<Item = i32> + Send + 'static {
64        use async_signal::{Signal, Signals};
65        use signal_hook::consts::signal::*;
66        let signals: Vec<Signal> = signals
67            .into_iter()
68            .filter_map(|n| match n {
69                SIGHUP => Some(Signal::Hup),
70                SIGINT => Some(Signal::Int),
71                SIGQUIT => Some(Signal::Quit),
72                SIGILL => Some(Signal::Ill),
73                SIGTRAP => Some(Signal::Trap),
74                SIGABRT => Some(Signal::Abort),
75                SIGBUS => Some(Signal::Bus),
76                SIGFPE => Some(Signal::Fpe),
77                SIGKILL => Some(Signal::Kill),
78                SIGUSR1 => Some(Signal::Usr1),
79                SIGSEGV => Some(Signal::Segv),
80                SIGUSR2 => Some(Signal::Usr2),
81                SIGPIPE => Some(Signal::Pipe),
82                SIGALRM => Some(Signal::Alarm),
83                SIGTERM => Some(Signal::Term),
84                SIGCHLD => Some(Signal::Child),
85                SIGCONT => Some(Signal::Cont),
86                SIGSTOP => Some(Signal::Stop),
87                SIGTSTP => Some(Signal::Tstp),
88                SIGTTIN => Some(Signal::Ttin),
89                SIGTTOU => Some(Signal::Ttou),
90                SIGURG => Some(Signal::Urg),
91                SIGXCPU => Some(Signal::Xcpu),
92                SIGXFSZ => Some(Signal::Xfsz),
93                SIGVTALRM => Some(Signal::Vtalarm),
94                SIGPROF => Some(Signal::Prof),
95                SIGWINCH => Some(Signal::Winch),
96                SIGIO => Some(Signal::Io),
97                SIGSYS => Some(Signal::Sys),
98                _ => None,
99            })
100            .collect();
101        Signals::new(signals)
102            .unwrap()
103            .filter_map(|r| r.ok().map(|s| s as i32))
104    }
105}
106
107impl SmolRuntime {
108    /// Spawn a future on the runtime, returning a future that has detach-on-drop semantics
109    ///
110    /// Spawned tasks conform to the following behavior:
111    ///
112    /// * detach on drop: If the returned [`DroppableFuture`] is dropped immediately, the task will
113    ///   continue to execute until completion.
114    ///
115    /// * unwinding: If the spawned future panics, this must not propagate to the join handle.
116    ///   Instead, the awaiting the join handle returns None in case of panic.
117    pub fn spawn<Fut>(
118        &self,
119        fut: Fut,
120    ) -> DroppableFuture<impl Future<Output = Option<Fut::Output>> + Send + 'static + use<Fut>>
121    where
122        Fut: Future + Send + 'static,
123        Fut::Output: Send + 'static,
124    {
125        let join_handle = DetachOnDrop(Some(async_global_executor::spawn(fut)));
126        DroppableFuture::new(async move { join_handle.catch_unwind().await.ok() })
127    }
128
129    /// Wake in this amount of wall time
130    pub async fn delay(&self, duration: Duration) {
131        Timer::after(duration).await;
132    }
133
134    /// Returns a [`Stream`] that yields a `()` on the provided period
135    pub fn interval(&self, period: Duration) -> impl Stream<Item = ()> + Send + 'static + use<> {
136        Timer::interval(period).map(|_| ())
137    }
138
139    /// Runtime implementation hook for blocking on a top level future.
140    pub fn block_on<Fut: Future>(&self, fut: Fut) -> Fut::Output {
141        RuntimeTrait::block_on(self, fut)
142    }
143
144    /// Race a future against the provided duration, returning None in case of timeout.
145    pub async fn timeout<Fut>(&self, duration: Duration, fut: Fut) -> Option<Fut::Output>
146    where
147        Fut: Future + Send,
148        Fut::Output: Send + 'static,
149    {
150        RuntimeTrait::timeout(self, duration, fut).await
151    }
152}
153
154impl From<SmolRuntime> for Runtime {
155    fn from(value: SmolRuntime) -> Self {
156        Arc::new(value).into()
157    }
158}