Skip to content

Commit bdcf27c

Browse files
Pauanalexcrichton
authored andcommitted
Major improvements to wasm-bindgen-futures (#1760)
This PR contains a few major improvements: * Code duplication has been removed. * Everything has been refactored so that the implementation is much easier to understand. * `future_to_promise` is now implemented with `spawn_local` rather than the other way around (this means `spawn_local` is faster since it doesn't need to create an unneeded `Promise`). * Both the single threaded and multi threaded executors have been rewritten from scratch: * They only create 1-2 allocations in Rust per Task, and all of the allocations happen when the Task is created. * The singlethreaded executor creates 1 Promise per tick, rather than 1 Promise per tick per Task. * Both executors do *not* create `Closure`s during polling, instead all needed `Closure`s are created ahead of time. * Both executors now have correct behavior with regard to spurious wakeups and waking up during the call to `poll`. * Both executors cache the `Waker` so it doesn't need to be recreated all the time. I believe both executors are now optimal in terms of both Rust and JS performance.
1 parent 0b1a764 commit bdcf27c

File tree

10 files changed

+585
-471
lines changed

10 files changed

+585
-471
lines changed

crates/futures/src/lib.rs

+199-24
Original file line numberDiff line numberDiff line change
@@ -7,46 +7,221 @@
77
//! ability to interoperate with JavaScript events and JavaScript I/O
88
//! primitives.
99
//!
10-
//! There are two main interfaces in this crate currently:
10+
//! There are three main interfaces in this crate currently:
1111
//!
1212
//! 1. [**`JsFuture`**](./struct.JsFuture.html)
1313
//!
1414
//! A type that is constructed with a `Promise` and can then be used as a
15-
//! `Future<Item = JsValue, Error = JsValue>`. This Rust future will resolve
15+
//! `Future<Output = Result<JsValue, JsValue>>`. This Rust future will resolve
1616
//! or reject with the value coming out of the `Promise`.
1717
//!
1818
//! 2. [**`future_to_promise`**](./fn.future_to_promise.html)
1919
//!
20-
//! Converts a Rust `Future<Item = JsValue, Error = JsValue>` into a
20+
//! Converts a Rust `Future<Output = Result<JsValue, JsValue>>` into a
2121
//! JavaScript `Promise`. The future's result will translate to either a
22-
//! rejected or resolved `Promise` in JavaScript.
22+
//! resolved or rejected `Promise` in JavaScript.
2323
//!
24-
//! These two items should provide enough of a bridge to interoperate the two
25-
//! systems and make sure that Rust/JavaScript can work together with
26-
//! asynchronous and I/O work.
24+
//! 3. [**`spawn_local`**](./fn.spawn_local.html)
2725
//!
28-
//! # Example Usage
26+
//! Spawns a `Future<Output = ()>` on the current thread. This is the
27+
//! best way to run a `Future` in Rust without sending it to JavaScript.
2928
//!
30-
//! This example wraps JavaScript's `Promise.resolve()` into a Rust `Future` for
31-
//! running tasks on the next tick of the micro task queue. The futures built on
32-
//! top of it can be scheduled for execution by conversion into a JavaScript
33-
//! `Promise`.
29+
//! These three items should provide enough of a bridge to interoperate the two
30+
//! systems and make sure that Rust/JavaScript can work together with
31+
//! asynchronous and I/O work.
3432
3533
#![cfg_attr(target_feature = "atomics", feature(stdsimd))]
3634
#![deny(missing_docs)]
3735

38-
use cfg_if::cfg_if;
36+
use js_sys::Promise;
37+
use std::cell::RefCell;
38+
use std::fmt;
39+
use std::future::Future;
40+
use std::pin::Pin;
41+
use std::rc::Rc;
42+
use std::task::{Context, Poll, Waker};
43+
use wasm_bindgen::prelude::*;
44+
45+
mod queue;
46+
47+
mod task {
48+
use cfg_if::cfg_if;
49+
50+
cfg_if! {
51+
if #[cfg(target_feature = "atomics")] {
52+
mod wait_async_polyfill;
53+
mod multithread;
54+
pub(crate) use multithread::*;
55+
56+
} else {
57+
mod singlethread;
58+
pub(crate) use singlethread::*;
59+
}
60+
}
61+
}
62+
63+
/// Runs a Rust `Future` on the current thread.
64+
///
65+
/// The `future` must be `'static` because it will be scheduled
66+
/// to run in the background and cannot contain any stack references.
67+
///
68+
/// The `future` will always be run on the next microtask tick even if it
69+
/// immediately returns `Poll::Ready`.
70+
///
71+
/// # Panics
72+
///
73+
/// This function has the same panic behavior as `future_to_promise`.
74+
#[inline]
75+
pub fn spawn_local<F>(future: F)
76+
where
77+
F: Future<Output = ()> + 'static,
78+
{
79+
task::Task::spawn(Box::pin(future));
80+
}
81+
82+
struct Inner {
83+
result: Option<Result<JsValue, JsValue>>,
84+
task: Option<Waker>,
85+
callbacks: Option<(Closure<dyn FnMut(JsValue)>, Closure<dyn FnMut(JsValue)>)>,
86+
}
87+
88+
/// A Rust `Future` backed by a JavaScript `Promise`.
89+
///
90+
/// This type is constructed with a JavaScript `Promise` object and translates
91+
/// it to a Rust `Future`. This type implements the `Future` trait from the
92+
/// `futures` crate and will either succeed or fail depending on what happens
93+
/// with the JavaScript `Promise`.
94+
///
95+
/// Currently this type is constructed with `JsFuture::from`.
96+
pub struct JsFuture {
97+
inner: Rc<RefCell<Inner>>,
98+
}
99+
100+
impl fmt::Debug for JsFuture {
101+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
102+
write!(f, "JsFuture {{ ... }}")
103+
}
104+
}
105+
106+
impl From<Promise> for JsFuture {
107+
fn from(js: Promise) -> JsFuture {
108+
// Use the `then` method to schedule two callbacks, one for the
109+
// resolved value and one for the rejected value. We're currently
110+
// assuming that JS engines will unconditionally invoke precisely one of
111+
// these callbacks, no matter what.
112+
//
113+
// Ideally we'd have a way to cancel the callbacks getting invoked and
114+
// free up state ourselves when this `JsFuture` is dropped. We don't
115+
// have that, though, and one of the callbacks is likely always going to
116+
// be invoked.
117+
//
118+
// As a result we need to make sure that no matter when the callbacks
119+
// are invoked they are valid to be called at any time, which means they
120+
// have to be self-contained. Through the `Closure::once` and some
121+
// `Rc`-trickery we can arrange for both instances of `Closure`, and the
122+
// `Rc`, to all be destroyed once the first one is called.
123+
let state = Rc::new(RefCell::new(Inner {
124+
result: None,
125+
task: None,
126+
callbacks: None,
127+
}));
128+
129+
fn finish(state: &RefCell<Inner>, val: Result<JsValue, JsValue>) {
130+
let task = {
131+
let mut state = state.borrow_mut();
132+
debug_assert!(state.callbacks.is_some());
133+
debug_assert!(state.result.is_none());
134+
135+
// First up drop our closures as they'll never be invoked again and
136+
// this is our chance to clean up their state.
137+
drop(state.callbacks.take());
138+
139+
// Next, store the value into the internal state.
140+
state.result = Some(val);
141+
state.task.take()
142+
};
143+
144+
// And then finally if any task was waiting on the value wake it up and
145+
// let them know it's there.
146+
if let Some(task) = task {
147+
task.wake()
148+
}
149+
}
150+
151+
let resolve = {
152+
let state = state.clone();
153+
Closure::once(move |val| finish(&state, Ok(val)))
154+
};
155+
156+
let reject = {
157+
let state = state.clone();
158+
Closure::once(move |val| finish(&state, Err(val)))
159+
};
160+
161+
js.then2(&resolve, &reject);
162+
163+
state.borrow_mut().callbacks = Some((resolve, reject));
164+
165+
JsFuture { inner: state }
166+
}
167+
}
168+
169+
impl Future for JsFuture {
170+
type Output = Result<JsValue, JsValue>;
171+
172+
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
173+
let mut inner = self.inner.borrow_mut();
174+
175+
// If our value has come in then we return it...
176+
if let Some(val) = inner.result.take() {
177+
return Poll::Ready(val);
178+
}
179+
180+
// ... otherwise we arrange ourselves to get woken up once the value
181+
// does come in
182+
inner.task = Some(cx.waker().clone());
183+
Poll::Pending
184+
}
185+
}
186+
187+
/// Converts a Rust `Future` into a JavaScript `Promise`.
188+
///
189+
/// This function will take any future in Rust and schedule it to be executed,
190+
/// returning a JavaScript `Promise` which can then be passed to JavaScript.
191+
///
192+
/// The `future` must be `'static` because it will be scheduled to run in the
193+
/// background and cannot contain any stack references.
194+
///
195+
/// The returned `Promise` will be resolved or rejected when the future completes,
196+
/// depending on whether it finishes with `Ok` or `Err`.
197+
///
198+
/// # Panics
199+
///
200+
/// Note that in wasm panics are currently translated to aborts, but "abort" in
201+
/// this case means that a JavaScript exception is thrown. The wasm module is
202+
/// still usable (likely erroneously) after Rust panics.
203+
///
204+
/// If the `future` provided panics then the returned `Promise` **will not
205+
/// resolve**. Instead it will be a leaked promise. This is an unfortunate
206+
/// limitation of wasm currently that's hoped to be fixed one day!
207+
pub fn future_to_promise<F>(future: F) -> Promise
208+
where
209+
F: Future<Output = Result<JsValue, JsValue>> + 'static,
210+
{
211+
let mut future = Some(future);
39212

40-
mod shared;
41-
pub use shared::*;
213+
Promise::new(&mut |resolve, reject| {
214+
let future = future.take().unwrap_throw();
42215

43-
cfg_if! {
44-
if #[cfg(target_feature = "atomics")] {
45-
mod wait_async_polyfill;
46-
mod multithread;
47-
pub use multithread::*;
48-
} else {
49-
mod singlethread;
50-
pub use singlethread::*;
51-
}
216+
spawn_local(async move {
217+
match future.await {
218+
Ok(val) => {
219+
resolve.call1(&JsValue::undefined(), &val).unwrap_throw();
220+
}
221+
Err(val) => {
222+
reject.call1(&JsValue::undefined(), &val).unwrap_throw();
223+
}
224+
}
225+
});
226+
})
52227
}

0 commit comments

Comments
 (0)