Skip to main content

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