executor_core/
web.rs

1//! Integration for Web/WASM environments.
2//!
3//! This module provides executor implementations for web browsers and other
4//! WASM environments using `wasm-bindgen-futures`.
5
6use crate::{Executor, LocalExecutor, Task};
7use core::{
8    future::Future,
9    mem::ManuallyDrop,
10    pin::Pin,
11    task::{Context, Poll},
12};
13use wasm_bindgen_futures::spawn_local;
14
15/// Web-based executor implementation for WASM targets.
16///
17/// This executor uses `wasm-bindgen-futures::spawn_local` to execute futures
18/// in web environments. Both `Send` and non-`Send` futures are handled the same
19/// way since web environments are single-threaded.
20///
21/// ## Panic Handling
22///
23/// Unlike other executors, the web executor cannot catch panics due to WASM limitations.
24/// If a spawned task panics, the entire WASM module will terminate. This is a fundamental
25/// limitation of the WASM environment and cannot be worked around.
26///
27#[derive(Clone, Copy, Debug)]
28pub struct WebExecutor;
29
30impl WebExecutor {
31    /// Create a new [`WebExecutor`].
32    pub fn new() -> Self {
33        Self
34    }
35}
36
37impl Default for WebExecutor {
38    fn default() -> Self {
39        Self::new()
40    }
41}
42
43/// Task wrapper for web/WASM environment.
44///
45/// This task type provides task management for web environments where
46/// panic catching is not available. Unlike other task implementations,
47/// panics cannot be caught and will terminate the entire WASM module.
48pub struct WebTask<T> {
49    inner: ManuallyDrop<Option<async_task::Task<T>>>,
50}
51
52impl<T> core::fmt::Debug for WebTask<T> {
53    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
54        f.debug_struct("WebTask").finish_non_exhaustive()
55    }
56}
57
58impl<T> Future for WebTask<T> {
59    type Output = T;
60
61    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
62        let mut this = self.as_mut();
63        let task = this
64            .inner
65            .as_mut()
66            .expect("Task has already been cancelled");
67        // In web environments, we can't catch panics, so just poll directly
68        let mut pinned_task = core::pin::pin!(task);
69        pinned_task.as_mut().poll(cx)
70    }
71}
72
73impl<T: 'static> Task<T> for WebTask<T> {
74    fn poll_result(
75        mut self: Pin<&mut Self>,
76        cx: &mut Context<'_>,
77    ) -> Poll<Result<T, crate::Error>> {
78        let mut this = self.as_mut();
79        let task = this
80            .inner
81            .as_mut()
82            .expect("Task has already been cancelled");
83        // In web environments, we can't catch panics
84        // If the task panics, the entire WASM module will terminate
85        let mut pinned_task = core::pin::pin!(task);
86        match pinned_task.as_mut().poll(cx) {
87            Poll::Ready(value) => Poll::Ready(Ok(value)),
88            Poll::Pending => Poll::Pending,
89        }
90    }
91
92    fn poll_cancel(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
93        // Cancel the underlying task
94        let this = unsafe { self.get_unchecked_mut() };
95        if let Some(task) = this.inner.take() {
96            // Schedule the cancellation but don't wait for it
97            wasm_bindgen_futures::spawn_local(async move {
98                let _ = task.cancel().await;
99            });
100        }
101        Poll::Ready(())
102    }
103}
104
105impl LocalExecutor for WebExecutor {
106    type Task<T: 'static> = WebTask<T>;
107
108    fn spawn<Fut>(&self, fut: Fut) -> Self::Task<Fut::Output>
109    where
110        Fut: Future + 'static,
111    {
112        let (runnable, task) = async_task::spawn_local(fut, |runnable: async_task::Runnable| {
113            spawn_local(async move {
114                runnable.run();
115            });
116        });
117        runnable.schedule();
118        WebTask {
119            inner: ManuallyDrop::new(Some(task)),
120        }
121    }
122}
123
124impl Executor for WebExecutor {
125    type Task<T: Send + 'static> = WebTask<T>;
126
127    fn spawn<Fut>(&self, fut: Fut) -> Self::Task<Fut::Output>
128    where
129        Fut: Future<Output: Send> + Send + 'static,
130    {
131        // In web environment, we use spawn_local even for Send futures
132        // since web workers don't have the same threading model as native
133        let (runnable, task) = async_task::spawn_local(fut, |runnable: async_task::Runnable| {
134            spawn_local(async move {
135                runnable.run();
136            });
137        });
138        runnable.schedule();
139        WebTask {
140            inner: ManuallyDrop::new(Some(task)),
141        }
142    }
143}