diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f449256e10..56fb600f0c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,11 +11,6 @@ * Add bindings for `CanvasState.reset()`, affecting `CanvasRenderingContext2D` and `OffscreenCanvasRenderingContext2D`. [#3844](https://github.com/rustwasm/wasm-bindgen/pull/3844) -### Fixed - -* Allow `wasm-bindgen-futures` to run correctly when using the atomics target feature in an environment that has no support for `Atomics.waitAsync()` and without cross-origin isolation. - [#3848](https://github.com/rustwasm/wasm-bindgen/pull/3848) - -------------------------------------------------------------------------------- ## [0.2.91](https://github.com/rustwasm/wasm-bindgen/compare/0.2.90...0.2.91) diff --git a/crates/futures/src/lib.rs b/crates/futures/src/lib.rs index 0e43314bf1a..a44ec7c6d27 100644 --- a/crates/futures/src/lib.rs +++ b/crates/futures/src/lib.rs @@ -50,40 +50,18 @@ pub use js_sys; pub use wasm_bindgen; mod task { - use std::future::Future; - use std::pin::Pin; - use cfg_if::cfg_if; - #[cfg(target_feature = "atomics")] - mod multithread; - mod singlethread; - #[cfg(target_feature = "atomics")] - mod wait_async_polyfill; - - pub(crate) fn spawn(future: Pin + 'static>>) { - cfg_if! { - if #[cfg(target_feature = "atomics")] { - #[wasm_bindgen::prelude::wasm_bindgen] - extern "C" { - /// Returns [`crossOriginIsolated`](https://developer.mozilla.org/en-US/docs/Web/API/crossOriginIsolated) global property. - #[wasm_bindgen(js_name = crossOriginIsolated)] - static CROSS_ORIGIN_ISOLATED: bool; - } - - if *CROSS_ORIGIN_ISOLATED { - multithread::Task::spawn(future) - } else { - singlethread::Task::spawn(future) - } - } else { - singlethread::Task::spawn(future) - } - } - } + cfg_if! { + if #[cfg(target_feature = "atomics")] { + mod wait_async_polyfill; + mod multithread; + pub(crate) use multithread::*; - pub(crate) trait Task { - fn run(&self); + } else { + mod singlethread; + pub(crate) use singlethread::*; + } } } @@ -103,7 +81,7 @@ pub fn spawn_local(future: F) where F: Future + 'static, { - task::spawn(Box::pin(future)); + task::Task::spawn(Box::pin(future)); } struct Inner { diff --git a/crates/futures/src/queue.rs b/crates/futures/src/queue.rs index f2162e60240..2e17eb5ecf1 100644 --- a/crates/futures/src/queue.rs +++ b/crates/futures/src/queue.rs @@ -19,7 +19,7 @@ struct QueueState { // The queue of Tasks which are to be run in order. In practice this is all the // synchronous work of futures, and each `Task` represents calling `poll` on // a future "at the right time". - tasks: RefCell>>, + tasks: RefCell>>, // This flag indicates whether we've scheduled `run_all` to run in the future. // This is used to ensure that it's only scheduled once. @@ -58,7 +58,7 @@ pub(crate) struct Queue { impl Queue { // Schedule a task to run on the next tick - pub(crate) fn schedule_task(&self, task: Rc) { + pub(crate) fn schedule_task(&self, task: Rc) { self.state.tasks.borrow_mut().push_back(task); // Use queueMicrotask to execute as soon as possible. If it does not exist // fall back to the promise resolution @@ -71,7 +71,7 @@ impl Queue { } } // Append a task to the currently running queue, or schedule it - pub(crate) fn push_task(&self, task: Rc) { + pub(crate) fn push_task(&self, task: Rc) { // It would make sense to run this task on the same tick. For now, we // make the simplifying choice of always scheduling tasks for a future tick. self.schedule_task(task) diff --git a/crates/futures/src/task/multithread.rs b/crates/futures/src/task/multithread.rs index 73bd2b723cc..6cfdfcb5437 100644 --- a/crates/futures/src/task/multithread.rs +++ b/crates/futures/src/task/multithread.rs @@ -1,4 +1,3 @@ -use super::Task as _; use std::cell::RefCell; use std::future::Future; use std::mem::ManuallyDrop; @@ -85,8 +84,27 @@ pub(crate) struct Task { inner: RefCell>, } -impl super::Task for Task { - fn run(&self) { +impl Task { + pub(crate) fn spawn(future: Pin + 'static>>) { + let atomic = AtomicWaker::new(); + let waker = unsafe { Waker::from_raw(AtomicWaker::into_raw_waker(atomic.clone())) }; + let this = Rc::new(Task { + atomic, + waker, + inner: RefCell::new(None), + }); + + let closure = { + let this = Rc::clone(&this); + Closure::new(move |_| this.run()) + }; + *this.inner.borrow_mut() = Some(Inner { future, closure }); + + // Queue up the Future's work to happen on the next microtask tick. + crate::queue::QUEUE.with(move |queue| queue.schedule_task(this)); + } + + pub(crate) fn run(&self) { let mut borrow = self.inner.borrow_mut(); // Same as `singlethread.rs`, handle spurious wakeups happening after we @@ -144,27 +162,6 @@ impl super::Task for Task { } } -impl Task { - pub(crate) fn spawn(future: Pin + 'static>>) { - let atomic = AtomicWaker::new(); - let waker = unsafe { Waker::from_raw(AtomicWaker::into_raw_waker(atomic.clone())) }; - let this = Rc::new(Task { - atomic, - waker, - inner: RefCell::new(None), - }); - - let closure = { - let this = Rc::clone(&this); - Closure::new(move |_| this.run()) - }; - *this.inner.borrow_mut() = Some(Inner { future, closure }); - - // Queue up the Future's work to happen on the next microtask tick. - crate::queue::QUEUE.with(move |queue| queue.schedule_task(this)); - } -} - fn wait_async(ptr: &AtomicI32, current_value: i32) -> Option { // If `Atomics.waitAsync` isn't defined then we use our fallback, otherwise // we use the native function. diff --git a/crates/futures/src/task/singlethread.rs b/crates/futures/src/task/singlethread.rs index 3f561f6d8a3..ad52f552400 100644 --- a/crates/futures/src/task/singlethread.rs +++ b/crates/futures/src/task/singlethread.rs @@ -21,38 +21,6 @@ pub(crate) struct Task { is_queued: Cell, } -impl super::Task for Task { - fn run(&self) { - let mut borrow = self.inner.borrow_mut(); - - // Wakeups can come in after a Future has finished and been destroyed, - // so handle this gracefully by just ignoring the request to run. - let inner = match borrow.as_mut() { - Some(inner) => inner, - None => return, - }; - - // Ensure that if poll calls `waker.wake()` we can get enqueued back on - // the run queue. - self.is_queued.set(false); - - let poll = { - let mut cx = Context::from_waker(&inner.waker); - inner.future.as_mut().poll(&mut cx) - }; - - // If a future has finished (`Ready`) then clean up resources associated - // with the future ASAP. This ensures that we don't keep anything extra - // alive in-memory by accident. Our own struct, `Rc` won't - // actually go away until all wakers referencing us go away, which may - // take quite some time, so ensure that the heaviest of resources are - // released early. - if poll.is_ready() { - *borrow = None; - } - } -} - impl Task { pub(crate) fn spawn(future: Pin + 'static>>) { let this = Rc::new(Self { @@ -129,4 +97,34 @@ impl Task { RawWaker::new(Rc::into_raw(this) as *const (), &VTABLE) } + + pub(crate) fn run(&self) { + let mut borrow = self.inner.borrow_mut(); + + // Wakeups can come in after a Future has finished and been destroyed, + // so handle this gracefully by just ignoring the request to run. + let inner = match borrow.as_mut() { + Some(inner) => inner, + None => return, + }; + + // Ensure that if poll calls `waker.wake()` we can get enqueued back on + // the run queue. + self.is_queued.set(false); + + let poll = { + let mut cx = Context::from_waker(&inner.waker); + inner.future.as_mut().poll(&mut cx) + }; + + // If a future has finished (`Ready`) then clean up resources associated + // with the future ASAP. This ensures that we don't keep anything extra + // alive in-memory by accident. Our own struct, `Rc` won't + // actually go away until all wakers referencing us go away, which may + // take quite some time, so ensure that the heaviest of resources are + // released early. + if poll.is_ready() { + *borrow = None; + } + } }