incular_runtime/
executor.rs1use std::{future::Future, time::Duration};
11
12use crate::tasks::TaskFailure;
13
14pub(crate) type Completion<T> = Box<dyn FnOnce(Result<T, TaskFailure>) + Send>;
15
16#[cfg(not(target_arch = "wasm32"))]
17pub(crate) use native::{AbortHandle, Executor};
18#[cfg(target_arch = "wasm32")]
19pub(crate) use web::{AbortHandle, Executor};
20
21pub async fn sleep(duration: Duration) {
28 #[cfg(not(target_arch = "wasm32"))]
29 tokio::time::sleep(duration).await;
30 #[cfg(target_arch = "wasm32")]
31 web::sleep(duration).await;
32}
33
34#[cfg(not(target_arch = "wasm32"))]
35mod native {
36 use super::{Completion, Duration, Future, TaskFailure};
37 use tokio::runtime::{Builder, Handle, Runtime};
38
39 pub(crate) use tokio::task::AbortHandle;
40
41 const SHUTDOWN_TIMEOUT: Duration = Duration::from_millis(100);
42
43 pub(crate) struct Executor {
44 runtime: std::cell::OnceCell<Runtime>,
47 stopped: Option<Handle>,
50 }
51
52 impl Executor {
53 pub(crate) fn new() -> Self {
54 Self {
55 runtime: std::cell::OnceCell::new(),
56 stopped: None,
57 }
58 }
59
60 pub(crate) fn handle(&self) -> Handle {
61 if let Some(stopped) = &self.stopped {
62 return stopped.clone();
63 }
64 self.runtime
65 .get_or_init(|| {
66 Builder::new_multi_thread()
67 .enable_time()
68 .enable_io()
69 .build()
70 .expect("Tokio runtime construction")
71 })
72 .handle()
73 .clone()
74 }
75
76 pub(crate) fn spawn<F, T>(&self, future: F, complete: Completion<T>) -> AbortHandle
77 where
78 F: Future<Output = T> + Send + 'static,
79 T: Send + 'static,
80 {
81 let handle = self.handle();
82 let join = handle.spawn(future);
83 let abort = join.abort_handle();
84 handle.spawn(async move { complete(join_outcome(join.await)) });
85 abort
86 }
87
88 pub(crate) fn spawn_blocking<T>(
89 &self,
90 work: impl FnOnce() -> T + Send + 'static,
91 complete: Completion<T>,
92 ) -> AbortHandle
93 where
94 T: Send + 'static,
95 {
96 let handle = self.handle();
97 let join = handle.spawn_blocking(work);
98 let abort = join.abort_handle();
99 handle.spawn(async move { complete(join_outcome(join.await)) });
100 abort
101 }
102
103 pub(crate) fn shutdown(&mut self) {
104 let runtime = self.runtime.take().unwrap_or_else(|| {
107 Builder::new_current_thread()
108 .build()
109 .expect("Tokio runtime construction")
110 });
111 self.stopped = Some(runtime.handle().clone());
112 runtime.shutdown_timeout(SHUTDOWN_TIMEOUT);
115 }
116 }
117
118 fn join_outcome<T>(result: Result<T, tokio::task::JoinError>) -> Result<T, TaskFailure> {
119 match result {
120 Ok(value) => Ok(value),
121 Err(error) if error.is_cancelled() => Err(TaskFailure::Cancelled),
122 Err(error) if error.is_panic() => Err(TaskFailure::Panicked),
123 Err(_) => Err(TaskFailure::RuntimeStopped),
124 }
125 }
126}
127
128#[cfg(target_arch = "wasm32")]
129mod web {
130 use std::{
131 pin::Pin,
132 sync::{
133 Arc,
134 atomic::{AtomicBool, Ordering},
135 },
136 task::{Context, Poll, Waker},
137 };
138
139 use super::{Completion, Duration, Future, TaskFailure};
140 use wasm_bindgen::{JsCast, prelude::*};
141
142 #[derive(Clone)]
146 pub(crate) struct AbortHandle {
147 aborted: Arc<AtomicBool>,
148 waker: Arc<std::sync::Mutex<Option<Waker>>>,
149 }
150
151 impl AbortHandle {
152 pub(crate) fn abort(&self) {
153 self.aborted.store(true, Ordering::Release);
154 if let Some(waker) = self.waker.lock().expect("abort waker mutex").take() {
155 waker.wake();
156 }
157 }
158 }
159
160 struct Abortable<F> {
161 future: Pin<Box<F>>,
162 handle: AbortHandle,
163 }
164
165 impl<F: Future> Future for Abortable<F> {
166 type Output = Result<F::Output, TaskFailure>;
167
168 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
169 if self.handle.aborted.load(Ordering::Acquire) {
170 return Poll::Ready(Err(TaskFailure::Cancelled));
171 }
172 *self.handle.waker.lock().expect("abort waker mutex") = Some(cx.waker().clone());
173 self.future.as_mut().poll(cx).map(Ok)
174 }
175 }
176
177 pub(crate) struct Executor {
178 stopped: bool,
179 }
180
181 impl Executor {
182 pub(crate) const fn new() -> Self {
183 Self { stopped: false }
184 }
185
186 pub(crate) fn spawn<F, T>(&self, future: F, complete: Completion<T>) -> AbortHandle
187 where
188 F: Future<Output = T> + Send + 'static,
189 T: Send + 'static,
190 {
191 let handle = AbortHandle {
192 aborted: Arc::new(AtomicBool::new(self.stopped)),
193 waker: Arc::new(std::sync::Mutex::new(None)),
194 };
195 let abortable = Abortable {
196 future: Box::pin(future),
197 handle: handle.clone(),
198 };
199 wasm_bindgen_futures::spawn_local(async move { complete(abortable.await) });
200 handle
201 }
202
203 pub(crate) fn spawn_blocking<T>(
204 &self,
205 work: impl FnOnce() -> T + Send + 'static,
206 complete: Completion<T>,
207 ) -> AbortHandle
208 where
209 T: Send + 'static,
210 {
211 self.spawn(async move { work() }, complete)
214 }
215
216 pub(crate) fn shutdown(&mut self) {
217 self.stopped = true;
218 }
219 }
220
221 #[wasm_bindgen]
222 extern "C" {
223 #[wasm_bindgen(js_name = setTimeout)]
224 fn set_timeout(handler: &js_sys::Function, timeout: i32) -> JsValue;
225 }
226
227 #[derive(Default)]
228 struct TimerState {
229 fired: bool,
230 waker: Option<Waker>,
231 }
232
233 struct Sleep {
237 millis: i32,
238 armed: bool,
239 state: Arc<std::sync::Mutex<TimerState>>,
240 }
241
242 impl Future for Sleep {
243 type Output = ();
244
245 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
246 let mut state = self.state.lock().expect("timer state mutex");
247 if state.fired {
248 return Poll::Ready(());
249 }
250 state.waker = Some(cx.waker().clone());
251 drop(state);
252 if !self.armed {
253 self.armed = true;
254 let shared = self.state.clone();
255 let fire = Closure::once_into_js(move || {
256 let mut state = shared.lock().expect("timer state mutex");
257 state.fired = true;
258 if let Some(waker) = state.waker.take() {
259 waker.wake();
260 }
261 });
262 let _ = set_timeout(fire.unchecked_ref(), self.millis);
263 }
264 Poll::Pending
265 }
266 }
267
268 pub(super) async fn sleep(duration: Duration) {
269 Sleep {
270 millis: i32::try_from(duration.as_millis()).unwrap_or(i32::MAX),
271 armed: false,
272 state: Arc::default(),
273 }
274 .await;
275 }
276}