async-rs/async-std

[tracking] browser WASM support

yoshuawuyts opened this issue ยท 19 comments

It'd be cool if async-std worked in the browser out of the box. Not everything can be supported, but we can support a smaller subset.

Tasks

  • no longer fail compilation under the --target wasm32-unknown-unknown target
  • make task::spawn work (use wasm_bindgen_futures::spawn_local)
  • expose stream
  • expose task::sleep
  • patch async-attributes to allow async fn main to work

References

Should we introduce async_std::os::wasm::task::spawn_local that calls wasm_bindgen_futures::spawn_local?

Or should we perhaps have async_std::task::spawn_local that doesn't have the Send bound and works on all platforms? On wasm it would call wasm_bindgen_futures::spawn_local and on other platforms it could do something else.

What if every thread was technically a worker thread onto which local tasks can be spawned? We could have thread-local task queues into which spawn_local pushes tasks. Regular worker threads would run tasks both from their regular queues and from those local queues, while other threads outside the runtime would run tasks from their local queues only when an invocation of async_std::task::block_on is idle.

Just some ideas...

(EDIT: I said something wrong regarding wasm-bindgen-futures, they recently upgraded to stable futures)

One challenge is that futures_timer will panic at runtime, because it calls std::time::Instant::now(), which panics in WASM.

Can we replace futures-timer with something else on wasm? Can we perhaps spawn a JS promise that completes after some number of seconds and then await that future?

Also, since Instant::now() panics on wasm, is it possible to get the current time on by invoking a JS function?

Can we replace futures-timer with something else on wasm? Can we perhaps spawn a JS promise that completes after some number of seconds and then await that future?

Yes, that should be possible. Example (with old futures): https://github.com/tomaka/wasm-timer/blob/fd76bd42824fcee5232959df3f8a15cb58e33c46/src/wasm.rs#L209-L278

Also, since Instant::now() panics on wasm, is it possible to get the current time on by invoking a JS function?

Yes. one can use performance.now().
Example: https://github.com/tomaka/wasm-timer/blob/366b8003189487d1468dd1b65be8523fb4ae4245/src/wasm.rs#L57-L64

The problem is that it is clearly a hack to assume that wasm32-unknown-unknown means "browser". As its name says, it's "unknown". I don't have a solution to that.

What if every thread was technically a worker thread onto which local tasks can be spawned? We could have thread-local task queues into which spawn_local pushes tasks.

@davidbarsky mentioned something like this earlier; that's very interesting!


@tomaka I was thinking of sidestepping futures-timer entirely and creating async bindings to the timing facilities directly. It seems like that would help with keeping bundle sizes down also.

Perhaps it would actually make sense to add a time submodule for WASM specifically so things like time::Instant::now() actually works? I think having that would actually be really valuable, and we could probably lean heavily on the work you've already done.

The problem is that it is clearly a hack to assume that wasm32-unknown-unknown means "browser".

I've talked with the wasm team about this, and they recommended to do feature detection for wasm-bindgen which is always passed (I believe). Afaik that should be the most reliable solution right now.

they recommended to do feature detection for wasm-bindgen which is always passed (I believe)

I don't understand what you mean by that?

@tomaka feature flag it as:

#[cfg(all(feature = "wasm-bindgen", target_arch = "wasm32"))]

as recommended by the wasm team. Not 100%, but better than just target_arch (:

So we would need three code paths:

  • The regular one (Unix, Windows)
  • The one with wasm32 + wasm-bindgen that invokes JS functions.
  • The one with only wasm32 that returns an error/panics all the time?

@tomaka yeah that's about it for now. Eventually we may want to support WASI too, but async support doesn't exist yet. We should engage the team more closely on this though; it's been a while since we last talked about it in-depth.

The core work of this happened in #757 and will be available in 1.6

@yoshuawuyts You have checked task::spawn, any idea on why it cannot be found when I run:

cargo check \             
--target  wasm32-unknown-unknown

It shows:

error[E0425]: cannot find function `spawn` in module `task`
  --> src/effector.rs:31:25
   |
31 |         let fut = task::spawn(async move {
   |                         ^^^^^ not found in `task`
   |
help: consider importing this function
   |
1  | use std::thread::spawn;
   |

error: aborting due to previous error

For more information about this error, try `rustc --explain E0425`.
error: could not compile `casbin`.

To learn more, run the command again with --verbose.

EDIT:

change to spawn_local works

i've got this error when running cargo check --target wasm32-unknown-unknown:

failed to resolve: use of undeclared type or module `futures_timer`
  --> /Users/admin/.cargo/registry/src/github.com-1ecc6299db9ec823/async-std-1.6.2/src/utils.rs:84:29
   |
84 |     pub(crate) struct Timer(futures_timer::Delay);
   |                             ^^^^^^^^^^^^^ use of undeclared type or module `futures_timer`

@sergeyt which vesion was that?

@dignifiedquire It says 1.6.2 :)

src/github.com-1ecc6299db9ec823/async-std-1.6.2/src/utils.rs:84:29

reading is hard :(

i've got this error when running cargo check --target wasm32-unknown-unknown:

failed to resolve: use of undeclared type or module `futures_timer`
  --> /Users/admin/.cargo/registry/src/github.com-1ecc6299db9ec823/async-std-1.6.2/src/utils.rs:84:29
   |
84 |     pub(crate) struct Timer(futures_timer::Delay);
   |                             ^^^^^^^^^^^^^ use of undeclared type or module `futures_timer`

I think you'll have to add the unstable feature to make this work

What are the next steps for this issue?

I am specifically interested in a WASM-friendly sleep. It looks like there is a working example of this from 3 years ago from the comment above:

https://github.com/richardanaya/asynctimer

Any progress since then?