1use std::future::Future;
45use std::pin::Pin;
46use std::sync::{Arc, Mutex, OnceLock, PoisonError};
47use std::task::{Context, Poll, Wake, Waker};
48
49use crate::abi::marshal::Sentinel;
50use crate::abi::{CancelToken, Cancellable, FfiError, GENERIC_ERROR_CODE};
51
52pub type BoxFuture = Pin<Box<dyn Future<Output = ()> + Send + 'static>>;
54
55pub trait Spawner: Send + Sync + 'static {
60 fn spawn(&self, fut: BoxFuture);
63}
64
65impl<F> Spawner for F
66where
67 F: Fn(BoxFuture) + Send + Sync + 'static,
68{
69 fn spawn(&self, fut: BoxFuture) {
70 self(fut);
71 }
72}
73
74static SPAWNER: OnceLock<Box<dyn Spawner>> = OnceLock::new();
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub struct SpawnerAlreadySet;
79
80impl std::fmt::Display for SpawnerAlreadySet {
81 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82 f.write_str("a WeaveFFI spawner has already been installed")
83 }
84}
85
86impl std::error::Error for SpawnerAlreadySet {}
87
88pub fn set_spawner(spawner: impl Spawner) -> Result<(), SpawnerAlreadySet> {
100 SPAWNER
101 .set(Box::new(spawner))
102 .map_err(|_| SpawnerAlreadySet)
103}
104
105#[derive(Debug, Clone, PartialEq, Eq)]
107pub enum SpawnError {
108 Failed(String),
110 Suspended,
113}
114
115impl std::fmt::Display for SpawnError {
116 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
117 match self {
118 Self::Failed(why) => write!(f, "could not start the async call: {why}"),
119 Self::Suspended => f.write_str("async function suspended with no executor on wasm32"),
120 }
121 }
122}
123
124impl std::error::Error for SpawnError {}
125
126pub fn spawn(fut: impl Future<Output = ()> + Send + 'static) -> Result<(), SpawnError> {
134 let fut: BoxFuture = Box::pin(fut);
135 match SPAWNER.get() {
136 Some(spawner) => std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
137 spawner.spawn(fut);
138 }))
139 .map_err(|payload| {
140 SpawnError::Failed(format!(
141 "the spawner panicked: {}",
142 crate::abi::panic_message(&*payload)
143 ))
144 }),
145 None => default_spawn(fut),
146 }
147}
148
149#[cfg(all(not(target_arch = "wasm32"), not(feature = "tokio")))]
150fn default_spawn(fut: BoxFuture) -> Result<(), SpawnError> {
151 pool::spawn(fut)
152}
153
154#[cfg(all(not(target_arch = "wasm32"), feature = "tokio"))]
155fn default_spawn(fut: BoxFuture) -> Result<(), SpawnError> {
156 if let Ok(handle) = tokio::runtime::Handle::try_current() {
157 drop(handle.spawn(fut));
158 return Ok(());
159 }
160 static RUNTIME: OnceLock<Result<tokio::runtime::Runtime, String>> = OnceLock::new();
161 match RUNTIME.get_or_init(|| {
162 tokio::runtime::Builder::new_multi_thread()
163 .thread_name("weaveffi-async")
164 .enable_all()
165 .build()
166 .map_err(|e| e.to_string())
167 }) {
168 Ok(rt) => {
169 drop(rt.spawn(fut));
170 Ok(())
171 }
172 Err(why) => Err(SpawnError::Failed(format!(
173 "could not start the Tokio runtime: {why}"
174 ))),
175 }
176}
177
178#[cfg(target_arch = "wasm32")]
179fn default_spawn(mut fut: BoxFuture) -> Result<(), SpawnError> {
180 use std::sync::atomic::{AtomicBool, Ordering};
181
182 struct Woken(AtomicBool);
183 impl Wake for Woken {
184 fn wake(self: Arc<Self>) {
185 self.0.store(true, Ordering::SeqCst);
186 }
187 fn wake_by_ref(self: &Arc<Self>) {
188 self.0.store(true, Ordering::SeqCst);
189 }
190 }
191
192 let woken = Arc::new(Woken(AtomicBool::new(false)));
193 let waker = Waker::from(Arc::clone(&woken));
194 let mut cx = Context::from_waker(&waker);
195 loop {
196 woken.0.store(false, Ordering::SeqCst);
197 if fut.as_mut().poll(&mut cx).is_ready() {
198 return Ok(());
199 }
200 if !woken.0.load(Ordering::SeqCst) {
201 drop(fut);
204 return Err(SpawnError::Suspended);
205 }
206 }
207}
208
209#[cfg(all(not(target_arch = "wasm32"), not(feature = "tokio")))]
212mod pool {
213 use std::collections::VecDeque;
214 use std::sync::atomic::{AtomicBool, Ordering};
215 use std::sync::{Arc, Condvar, Mutex, OnceLock, PoisonError};
216 use std::task::{Context, Wake, Waker};
217
218 use super::{BoxFuture, SpawnError};
219
220 struct Pool {
221 queue: Mutex<VecDeque<Arc<Task>>>,
222 ready: Condvar,
223 }
224
225 struct Task {
226 future: Mutex<Option<BoxFuture>>,
227 queued: AtomicBool,
230 pool: &'static Pool,
231 }
232
233 impl Task {
234 fn schedule(self: Arc<Self>) {
235 if !self.queued.swap(true, Ordering::AcqRel) {
236 let pool = self.pool;
237 pool.queue
238 .lock()
239 .unwrap_or_else(PoisonError::into_inner)
240 .push_back(self);
241 pool.ready.notify_one();
242 }
243 }
244 }
245
246 impl Wake for Task {
247 fn wake(self: Arc<Self>) {
248 self.schedule();
249 }
250 fn wake_by_ref(self: &Arc<Self>) {
251 Arc::clone(self).schedule();
252 }
253 }
254
255 impl Pool {
256 fn work(&'static self) {
257 loop {
258 let task = {
259 let mut queue = self.queue.lock().unwrap_or_else(PoisonError::into_inner);
260 loop {
261 match queue.pop_front() {
262 Some(task) => break task,
263 None => {
264 queue = self
265 .ready
266 .wait(queue)
267 .unwrap_or_else(PoisonError::into_inner);
268 }
269 }
270 }
271 };
272 task.queued.store(false, Ordering::Release);
275 let mut slot = task.future.lock().unwrap_or_else(PoisonError::into_inner);
276 if let Some(fut) = slot.as_mut() {
277 let waker = Waker::from(Arc::clone(&task));
278 if fut
281 .as_mut()
282 .poll(&mut Context::from_waker(&waker))
283 .is_ready()
284 {
285 *slot = None;
286 }
287 }
288 }
289 }
290 }
291
292 pub(super) fn size() -> usize {
294 std::thread::available_parallelism()
295 .map_or(2, std::num::NonZero::get)
296 .max(2)
297 }
298
299 fn pool() -> Result<&'static Pool, SpawnError> {
300 static POOL: OnceLock<Result<&'static Pool, String>> = OnceLock::new();
301 POOL.get_or_init(|| {
302 let pool: &'static Pool = Box::leak(Box::new(Pool {
303 queue: Mutex::new(VecDeque::new()),
304 ready: Condvar::new(),
305 }));
306 let started = (0..size())
307 .filter(|i| {
308 std::thread::Builder::new()
309 .name(format!("weaveffi-async-{i}"))
310 .spawn(move || pool.work())
311 .is_ok()
312 })
313 .count();
314 if started == 0 {
315 Err("no executor thread could be started".to_string())
316 } else {
317 Ok(pool)
318 }
319 })
320 .clone()
321 .map_err(SpawnError::Failed)
322 }
323
324 pub(super) fn spawn(fut: BoxFuture) -> Result<(), SpawnError> {
325 let pool = pool()?;
326 Arc::new(Task {
327 future: Mutex::new(Some(fut)),
328 queued: AtomicBool::new(false),
329 pool,
330 })
331 .schedule();
332 Ok(())
333 }
334}
335
336#[must_use]
340pub fn default_pool_size() -> usize {
341 #[cfg(all(not(target_arch = "wasm32"), not(feature = "tokio")))]
342 {
343 pool::size()
344 }
345 #[cfg(any(target_arch = "wasm32", feature = "tokio"))]
346 {
347 0
348 }
349}
350
351pub fn block_on<F: Future>(fut: F) -> F::Output {
366 struct ThreadWaker(std::thread::Thread);
367 impl Wake for ThreadWaker {
368 fn wake(self: Arc<Self>) {
369 self.0.unpark();
370 }
371 fn wake_by_ref(self: &Arc<Self>) {
372 self.0.unpark();
373 }
374 }
375
376 let mut fut = std::pin::pin!(fut);
377 let waker = Waker::from(Arc::new(ThreadWaker(std::thread::current())));
378 let mut cx = Context::from_waker(&waker);
379 loop {
380 match fut.as_mut().poll(&mut cx) {
381 Poll::Ready(out) => return out,
382 Poll::Pending => std::thread::park(),
383 }
384 }
385}
386
387struct Completion<C> {
390 state: Mutex<CompletionState<C>>,
391}
392
393struct CompletionState<C> {
394 complete: Option<C>,
395 launching: bool,
397 dropped: bool,
399}
400
401impl<C> Completion<C> {
402 fn lock(&self) -> std::sync::MutexGuard<'_, CompletionState<C>> {
403 self.state.lock().unwrap_or_else(PoisonError::into_inner)
404 }
405}
406
407struct Guard<T, C: FnOnce(Result<T, FfiError>)> {
411 shared: Arc<Completion<C>>,
412 _result: std::marker::PhantomData<fn(T)>,
413}
414
415impl<T, C: FnOnce(Result<T, FfiError>)> Guard<T, C> {
416 fn complete(self, result: Result<T, FfiError>) {
417 let complete = self.shared.lock().complete.take();
418 if let Some(c) = complete {
419 c(result);
420 }
421 }
422}
423
424impl<T, C: FnOnce(Result<T, FfiError>)> Drop for Guard<T, C> {
425 fn drop(&mut self) {
426 let complete = {
427 let mut state = self.shared.lock();
428 if state.launching {
429 state.dropped = true;
430 None
431 } else {
432 state.complete.take()
433 }
434 };
435 if let Some(c) = complete {
436 c(Err(FfiError::cancelled()));
437 }
438 }
439}
440
441pub fn run_async<T, Fut, C>(fut: Fut, cancel: Option<CancelToken>, complete: C)
457where
458 T: 'static,
459 Fut: Future<Output = Result<T, FfiError>> + Send + 'static,
460 C: FnOnce(Result<T, FfiError>) + Send + 'static,
461{
462 let shared = Arc::new(Completion {
463 state: Mutex::new(CompletionState {
464 complete: Some(complete),
465 launching: true,
466 dropped: false,
467 }),
468 });
469 let guard = Guard {
470 shared: Arc::clone(&shared),
471 _result: std::marker::PhantomData,
472 };
473 let task = async move {
474 let guard = guard;
475 let caught = CatchUnwind::new(fut);
476 let outcome = match cancel {
477 Some(token) => Cancellable::new(caught, token).await,
478 None => Some(caught.await),
479 };
480 guard.complete(match outcome {
481 None => Err(FfiError::cancelled()),
482 Some(Ok(result)) => result,
483 Some(Err(payload)) => Err(FfiError::from_panic(&*payload)),
484 });
485 };
486 let spawned = spawn(task);
487 let (complete, error) = {
488 let mut state = shared.lock();
489 state.launching = false;
490 match spawned {
491 Err(e) => (
492 state.complete.take(),
493 FfiError::new(GENERIC_ERROR_CODE, &e.to_string()),
494 ),
495 Ok(()) if state.dropped => (state.complete.take(), FfiError::cancelled()),
496 Ok(()) => (None, FfiError::default()),
497 }
498 };
499 if let Some(c) = complete {
500 c(Err(error));
501 }
502}
503
504pub fn launch_async<T, S, Fut>(
515 prepare: impl FnOnce() -> Result<(Fut, Option<CancelToken>), FfiError>,
516 lower: impl FnOnce(T) -> S + Send + 'static,
517 deliver: impl FnOnce(*mut FfiError, S) + Send + 'static,
518) where
519 T: 'static,
520 S: Sentinel,
521 Fut: Future<Output = Result<T, FfiError>> + Send + 'static,
522{
523 let finish = move |result: Result<T, FfiError>| {
524 let lowered = result.and_then(|value| {
525 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| lower(value)))
528 .map_err(|payload| FfiError::from_panic(&*payload))
529 });
530 match lowered {
531 Ok(slots) => deliver(std::ptr::null_mut(), slots),
532 Err(e) => deliver(crate::abi::boxed_error(e), S::sentinel()),
533 }
534 };
535 match std::panic::catch_unwind(std::panic::AssertUnwindSafe(prepare)) {
536 Ok(Ok((fut, cancel))) => run_async(fut, cancel, finish),
537 Ok(Err(e)) => finish(Err(e)),
538 Err(payload) => finish(Err(FfiError::from_panic(&*payload))),
539 }
540}
541
542pub struct CatchUnwind<F> {
550 inner: Option<Pin<Box<F>>>,
551}
552
553impl<F: Future> CatchUnwind<F> {
554 pub fn new(fut: F) -> Self {
556 Self {
557 inner: Some(Box::pin(fut)),
558 }
559 }
560}
561
562impl<F: Future> Future for CatchUnwind<F> {
563 type Output = Result<F::Output, Box<dyn std::any::Any + Send + 'static>>;
564
565 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
566 let this = &mut *self;
567 let Some(inner) = this.inner.as_mut() else {
568 return Poll::Pending;
571 };
572 match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| inner.as_mut().poll(cx))) {
573 Ok(Poll::Pending) => Poll::Pending,
574 Ok(Poll::Ready(out)) => {
575 this.inner = None;
576 Poll::Ready(Ok(out))
577 }
578 Err(payload) => {
579 this.inner = None;
580 Poll::Ready(Err(payload))
581 }
582 }
583 }
584}
585
586#[cfg(test)]
587mod tests {
588 use super::*;
589 use std::sync::mpsc;
590
591 #[test]
592 fn default_spawner_runs_the_future() {
593 let (tx, rx) = mpsc::channel();
594 spawn(async move {
595 tx.send(7).unwrap();
596 })
597 .unwrap();
598 assert_eq!(rx.recv_timeout(std::time::Duration::from_secs(30)), Ok(7));
599 }
600
601 #[test]
602 fn catch_unwind_reports_panics_and_values() {
603 let ok = block_on(CatchUnwind::new(async { 5 }));
604 assert_eq!(ok.unwrap(), 5);
605 let err = block_on(CatchUnwind::new(async {
606 if true {
607 panic!("boom");
608 }
609 1
610 }));
611 let payload = err.unwrap_err();
612 assert_eq!(crate::abi::panic_message(&*payload), "boom");
613 }
614
615 fn run(
616 fut: impl Future<Output = Result<i32, FfiError>> + Send + 'static,
617 cancel: Option<CancelToken>,
618 ) -> mpsc::Receiver<Result<i32, i32>> {
619 let (tx, rx) = mpsc::channel();
620 run_async(fut, cancel, move |r: Result<i32, FfiError>| {
621 tx.send(r.map_err(|e| e.code)).unwrap();
622 });
623 rx
624 }
625
626 const WAIT: std::time::Duration = std::time::Duration::from_secs(30);
627
628 #[test]
629 fn run_async_delivers_values_errors_and_panics() {
630 assert_eq!(run(async { Ok(4) }, None).recv_timeout(WAIT), Ok(Ok(4)));
631 let rx = run(async { Err(FfiError::new(9, "domain")) }, None);
632 assert_eq!(rx.recv_timeout(WAIT), Ok(Err(9)));
633 let rx = run(
634 async {
635 if true {
636 panic!("bug");
637 }
638 Ok(0)
639 },
640 None,
641 );
642 assert_eq!(rx.recv_timeout(WAIT), Ok(Err(crate::abi::PANIC_ERROR_CODE)));
643 }
644
645 #[test]
646 fn run_async_completes_cancelled_when_the_token_fires() {
647 let raw = crate::abi::cancel_token_create();
648 let token = unsafe { CancelToken::from_raw(raw) };
649 let rx = run(
650 async {
651 std::future::pending::<()>().await;
652 Ok(1)
653 },
654 Some(token),
655 );
656 unsafe { crate::abi::cancel_token_cancel(raw) };
657 unsafe { crate::abi::cancel_token_destroy(raw) };
658 assert_eq!(
659 rx.recv_timeout(WAIT),
660 Ok(Err(crate::abi::CANCELLED_ERROR_CODE))
661 );
662 assert!(rx
663 .recv_timeout(std::time::Duration::from_millis(50))
664 .is_err());
665 }
666
667 #[test]
668 fn a_dropped_guard_completes_cancelled_once() {
669 let (tx, rx) = mpsc::channel();
670 let shared = Arc::new(Completion {
671 state: Mutex::new(CompletionState {
672 complete: Some(move |r: Result<(), FfiError>| {
673 tx.send(r.map_err(|e| e.code)).unwrap();
674 }),
675 launching: false,
676 dropped: false,
677 }),
678 });
679 drop(Guard {
680 shared,
681 _result: std::marker::PhantomData,
682 });
683 assert_eq!(rx.recv(), Ok(Err(crate::abi::CANCELLED_ERROR_CODE)));
684 assert!(rx.recv().is_err());
685 }
686
687 type Prepared = Result<
688 (
689 std::future::Ready<Result<i32, FfiError>>,
690 Option<CancelToken>,
691 ),
692 FfiError,
693 >;
694
695 #[test]
696 fn a_panicking_prepare_completes_with_the_panic_code() {
697 let (tx, rx) = mpsc::channel();
698 launch_async(
699 || -> Prepared { panic!("lifting blew up") },
700 |v: i32| v,
701 move |err, v| {
702 let code = unsafe { err.as_ref() }.map_or(0, |e| e.code);
703 unsafe { crate::abi::error_free(err) };
704 tx.send((code, v)).unwrap();
705 },
706 );
707 assert_eq!(rx.recv_timeout(WAIT), Ok((crate::abi::PANIC_ERROR_CODE, 0)));
708 }
709}