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 // A poll can unwind via either a Rust panic or a foreign (JS)
143 // exception. `catch_unwind` only catches the former, but both run
144 // drops, so a drop guard covers both: if a poll unwinds we drop
145 // `Inner`, releasing the future and breaking the
146 // `Inner -> Waker -> Rc<Task>` cycle that would otherwise leak the
147 // `Task`. The guard is disarmed (forgotten) on a normal return.
148 struct ClearOnUnwind<'a>(&'a RefCell<Option<Inner>>);
149 impl Drop for ClearOnUnwind<'_> {
150 fn drop(&mut self) {
151 *self.0.borrow_mut() = None;
152 }
153 }
154 let clear_on_unwind = ClearOnUnwind(&self.inner);
155
156 let mut borrow = self.inner.borrow_mut();
157
158 // Wakeups can come in after a Future has finished and been destroyed,
159 // so handle this gracefully by just ignoring the request to run.
160 let inner = match borrow.as_mut() {
161 Some(inner) => inner,
162 None => {
163 core::mem::forget(clear_on_unwind);
164 return;
165 }
166 };
167
168 // Ensure that if poll calls `waker.wake()` we can get enqueued back on
169 // the run queue.
170 self.is_queued.set(false);
171
172 // In debug mode we want to avoid deeply nested stacks from individual
173 // `poll()` calls, so we use `task.run` on a task created per future.
174 #[cfg(debug_assertions)]
175 let is_ready = match self.console.as_ref() {
176 // Wrap `inner` in AssertUnwindSafe before capturing it, so the closure
177 // satisfies MaybeUnwindSafe (required when panic=unwind). This is safe:
178 // a panic from `is_ready` propagates as usual and is handled by the
179 // `ClearOnUnwind` guard above.
180 Some(console) => {
181 let mut inner = core::panic::AssertUnwindSafe(inner);
182 console.run(&mut move || inner.is_ready())
183 }
184 None => inner.is_ready(),
185 };
186
187 // In release mode we prefer to avoid the overhead of the JS wrapper
188 // and just poll directly.
189 #[cfg(not(debug_assertions))]
190 let is_ready = inner.is_ready();
191
192 // If a future has finished (`Ready`) then clean up resources associated
193 // with the future ASAP. This ensures that we don't keep anything extra
194 // alive in-memory by accident. Our own struct, `Rc<Task>` won't
195 // actually go away until all wakers referencing us go away, which may
196 // take quite some time, so ensure that the heaviest of resources are
197 // released early.
198 if is_ready {
199 *borrow = None;
200 }
201
202 core::mem::forget(clear_on_unwind);
203 }
204}