zesterer / flume

A safe and fast multi-producer, multi-consumer channel.
https://crates.io/crates/flume
Apache License 2.0
2.47k stars 85 forks source link

Senders and receivers are both unusable in browser main thread in WASM #137

Closed white-axe closed 3 months ago

white-axe commented 1 year ago

I made a WebAssembly example at https://github.com/white-axe/flume-wasm-atomics-test that uses flume to send data between the main thread of a web browser and a web worker.

However, it crashes sporadically with errors like this:

Uncaught (in promise) RuntimeError: Atomics.wait cannot be called in this context
    at core::core_arch::wasm32::atomic::memory_atomic_wait32::h96376a6414a86a87 (flume-wasm-atomics-test_bg.wasm:0xe12d2)
    at std::sys::wasm::futex::futex_wait::h292a0a973fc726d3 (flume-wasm-atomics-test_bg.wasm:0xa5924)
    at std::sys::wasm::locks::futex_mutex::Mutex::lock_contended::hd45bb12cab0c22e6 (flume-wasm-atomics-test_bg.wasm:0x702c4)
    at std::sys::wasm::locks::futex_mutex::Mutex::lock::h73cff9beb960ea5f (flume-wasm-atomics-test_bg.wasm:0x98dd2)
    at std::sync::mutex::Mutex<T>::lock::h1b6b582f0a571882 (flume-wasm-atomics-test_bg.wasm:0xdc6de)
    at flume::wait_lock::h1c6a6297de59474f (flume-wasm-atomics-test_bg.wasm:0xaa943)
    at flume::Shared<T>::recv::h41d724fcd222a605 (flume-wasm-atomics-test_bg.wasm:0x457ca)
    at flume::Shared<T>::recv_sync::h75768094a9f2c6b2 (flume-wasm-atomics-test_bg.wasm:0x9caf9)
    at flume::async::RecvFut<T>::poll_inner::hbd655e3c0d43e014 (flume-wasm-atomics-test_bg.wasm:0x328d0)
    at <flume::async::RecvFut<T> as core::future::future::Future>::poll::hbd3ea43ef3f7793f (flume-wasm-atomics-test_bg.wasm:0xca7d9)

This is happening because the wait_lock function is using a mutex or spinlock: https://github.com/zesterer/flume/blob/fcf384956a7badd003c4eca43da5174f4e0c86a0/src/lib.rs#L395-L416

In my example, it's using the mutex implementation of wait_lock and the mutex is causing the error because it's blocking in the main thread of a web browser, and this is not allowed. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/wait:

Note: This operation only works with an Int32Array or BigInt64Array that views a SharedArrayBuffer, and may not be allowed on the main thread. For a non-blocking, asynchronous version of this method, see Atomics.waitAsync().

My example itself doesn't block the main thread, it's just the wait_lock function that's blocking. If you look at my example, you'll notice I only used non-blocking code on the main thread.

However, using the spinlock implementation by enabling the spin feature doesn't work either because that uses thread::sleep which also blocks and therefore cannot be used in the main thread. This basically makes it impossible to use senders or receivers at all in a reliable way in multithreaded WebAssembly projects if either the sender or the receiver is on the main thread.

I propose to add a plain spinlock implementation of wait_lock without thread::sleep for WebAssembly like I did here: https://github.com/Astrabit-ST/flume/commit/d323799efea329c87a3a5a5b45cc76f46da278c2. Of course, this should be opt-in if it's implemented because not every WebAssembly project using flume is going to run into this issue.