wasm_bindgen_futures/task/
singlethread.rs

1use alloc::boxed::Box;
2use alloc::rc::Rc;
3use core::cell::{Cell, RefCell};
4use core::future::Future;
5use core::mem::ManuallyDrop;
6use core::pin::Pin;
7use core::task::{Context, RawWaker, RawWakerVTable, Waker};
8
9struct Inner {
10    future: Pin<Box<dyn Future<Output = ()> + 'static>>,
11    waker: Waker,
12}
13
14impl Inner {
15    fn is_ready(&mut self) -> bool {
16        let mut cx = Context::from_waker(&self.waker);
17        self.future.as_mut().poll(&mut cx).is_ready()
18    }
19}
20
21#[cfg(debug_assertions)]
22#[wasm_bindgen::prelude::wasm_bindgen]
23extern "C" {
24    type ConsoleTask;
25
26    #[wasm_bindgen(js_namespace = console, js_name = createTask, catch)]
27    fn create_task(name: &str) -> Result<ConsoleTask, wasm_bindgen::JsValue>;
28
29    #[wasm_bindgen(method)]
30    fn run(this: &ConsoleTask, poll: &mut dyn FnMut() -> bool) -> bool;
31}
32
33pub(crate) struct Task {
34    // Console tracking for this task to avoid deeply nested stacks from individual `poll()` calls.
35    // See [Linked Stack Traces](https://developer.chrome.com/blog/devtools-modern-web-debugging#linked_stack_traces).
36    #[cfg(debug_assertions)]
37    console: Option<ConsoleTask>,
38
39    // The actual Future that we're executing as part of this task.
40    //
41    // This is an Option so that the Future can be immediately dropped when it's
42    // finished
43    inner: RefCell<Option<Inner>>,
44
45    // This is used to ensure that the Task will only be queued once
46    is_queued: Cell<bool>,
47}
48
49impl Task {
50    pub(crate) fn spawn<F: Future<Output = ()> + 'static>(future: F) {
51        let this = Rc::new(Self {
52            #[cfg(debug_assertions)]
53            console: create_task(core::any::type_name::<F>()).ok(),
54            inner: RefCell::new(None),
55            is_queued: Cell::new(true),
56        });
57
58        let waker = unsafe { Waker::from_raw(Task::into_raw_waker(Rc::clone(&this))) };
59
60        *this.inner.borrow_mut() = Some(Inner {
61            future: Box::pin(future),
62            waker,
63        });
64
65        crate::queue::Queue::with(|queue| queue.schedule_task(this));
66    }
67
68    fn force_wake(this: Rc<Self>) {
69        crate::queue::Queue::with(|queue| {
70            queue.push_task(this);
71        });
72    }
73
74    fn wake(this: Rc<Self>) {
75        // If we've already been placed on the run queue then there's no need to
76        // requeue ourselves since we're going to run at some point in the
77        // future anyway.
78        if this.is_queued.replace(true) {
79            return;
80        }
81
82        Self::force_wake(this);
83    }
84
85    fn wake_by_ref(this: &Rc<Self>) {
86        // If we've already been placed on the run queue then there's no need to
87        // requeue ourselves since we're going to run at some point in the
88        // future anyway.
89        if this.is_queued.replace(true) {
90            return;
91        }
92
93        Self::force_wake(Rc::clone(this));
94    }
95
96    /// Creates a standard library `RawWaker` from an `Rc` of ourselves.
97    ///
98    /// Note that in general this is wildly unsafe because everything with
99    /// Futures requires `Sync` + `Send` with regard to Wakers. For wasm,
100    /// however, everything is guaranteed to be singlethreaded (since we're
101    /// compiled without the `atomics` feature) so we "safely lie" and say our
102    /// `Rc` pointer is good enough.
103    ///
104    /// The implementation is based off of futures::task::ArcWake
105    unsafe fn into_raw_waker(this: Rc<Self>) -> RawWaker {
106        unsafe fn raw_clone(ptr: *const ()) -> RawWaker {
107            let ptr = ManuallyDrop::new(Rc::from_raw(ptr as *const Task));
108            Task::into_raw_waker(Rc::clone(&ptr))
109        }
110
111        unsafe fn raw_wake(ptr: *const ()) {
112            let ptr = Rc::from_raw(ptr as *const Task);
113            Task::wake(ptr);
114        }
115
116        unsafe fn raw_wake_by_ref(ptr: *const ()) {
117            let ptr = ManuallyDrop::new(Rc::from_raw(ptr as *const Task));
118            Task::wake_by_ref(&ptr);
119        }
120
121        unsafe fn raw_drop(ptr: *const ()) {
122            drop(Rc::from_raw(ptr as *const Task));
123        }
124
125        static VTABLE: RawWakerVTable =
126            RawWakerVTable::new(raw_clone, raw_wake, raw_wake_by_ref, raw_drop);
127
128        RawWaker::new(Rc::into_raw(this) as *const (), &VTABLE)
129    }
130
131    pub(crate) fn run(&self) {
132        let mut borrow = self.inner.borrow_mut();
133
134        // Wakeups can come in after a Future has finished and been destroyed,
135        // so handle this gracefully by just ignoring the request to run.
136        let inner = match borrow.as_mut() {
137            Some(inner) => inner,
138            None => return,
139        };
140
141        // Ensure that if poll calls `waker.wake()` we can get enqueued back on
142        // the run queue.
143        self.is_queued.set(false);
144
145        // In debug mode we want to avoid deeply nested stacks from individual
146        // `poll()` calls, so we use `task.run` on a task created per future.
147        #[cfg(debug_assertions)]
148        let is_ready = match self.console.as_ref() {
149            Some(console) => console.run(&mut move || inner.is_ready()),
150            None => inner.is_ready(),
151        };
152
153        // In release mode we prefer to avoid the overhead of the JS wrapper
154        // and just poll directly.
155        #[cfg(not(debug_assertions))]
156        let is_ready = inner.is_ready();
157
158        // If a future has finished (`Ready`) then clean up resources associated
159        // with the future ASAP. This ensures that we don't keep anything extra
160        // alive in-memory by accident. Our own struct, `Rc<Task>` won't
161        // actually go away until all wakers referencing us go away, which may
162        // take quite some time, so ensure that the heaviest of resources are
163        // released early.
164        if is_ready {
165            *borrow = None;
166        }
167    }
168}