While I understand that JavaScript is inherently single-threaded and generally frowns upon such things, I am wondering if there is any way to get a WebWorker to wait until some data is made available from the main thread without destroying the call stack of the WebWorker.
As this is for a fun project, I can use new technologies and things that won't reliably run on older browsers, and I don't mind esoteric hacks as long as they work.
Some other solutions that I have thought about:
Continuously poll LocalStorage in a loop until there is data at a predetermined key. This would seem to work because LocalStorage updates by other threads should be visible to the current thread even when polled in a loop, judging from all the discussions about the thread safety of LocalStorage and having multiple tabs write to the same LocalStorage key. The drawback of this approach is that it isn't really "waiting", i.e. the worker thread still consumes full CPU usage hammering at LocalStorage. While LocalStorage is usually implemented with locks, it is not possible to hold a LocalStorage lock for extended periods of time (the lock is released once getItem
or setItem
returns).
ECMAScript 6 yield
. This doesn't work here because it requires all functions in the call stack (until the place you want to yield to) to be marked as generator functions. The place I want to pause my WebWorker has a call stack that contains WebAssembly functions, which can't be marked as generator functions.
IndexedDB. This doesn't work because IndexedDB does not support synchronous requests.
I am aware of this similar question, but that question specifically talks about the onmessage
event and was asked in 2012, before yield
and WebAssembly were introduced.
Is there any way to somehow simulate a lock on the WebWorker thread, or otherwise, so that it would wait until some data is available?
Edit: Note that SharedArrayBuffer
was disabled by default in all major browsers (on January 5th 2018) in response to Spectre, and hasn't been fully re-enabled yet.
JavaScript's SharedArrayBuffer sounds like a perfect fit for you:
For your purpose, you want to have the WebWorker wait until data is available. With SharedArrayBuffer
you can use a spinloop (Atomics.load
until the value changes) but it would be better to use Atomics.wait
until the other worker sends you an Atomics.notify
. This later API is heavily inspired by Linux's futex and won't needlessly spin if the value you're waiting on isn't available.
// Main program:
var w = new Worker("worker.js")
var sab = new SharedArrayBuffer(1024);
w.postMessage(sab);
var i = new Int32Array(sab);
// Maybe wait for worker.js to message back that it's ready through onmessage?
//
// Fill i with some data!
// ...
//
// Notify one worker, at location 0, with value 1.
Atomics.store(i, 0, 1);
Atomics.notify(i, 0, /* notify count */ 1);
// worker.js:
var sab;
var i;
onmessage = function (ev) {
sab = ev.data;
var i = new Int32Array(sab);
}
// Maybe tell the main program we're ready through postMessage?
//
// ...
// Wait until location 0 isn't value 0
Atomics.wait(i, 0, 0);
Remember: it's a bad idea to block the main thread! Your site will be unresponsive if you do that. Your question was asking about blocking a worker, but readers may be interested in waiting from the main thread. Don't!
A very similar, and compatible, API will eventually be available in WebAssembly. Here's an early draft proposal. When I say compatible: we expect that WebAssembly will be able to use the same SharedArrayBuffer
as JavaScript does, and both will be able to communicate through it seamlessly.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With