Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Obtain data synchronously from WebWorker?

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?

like image 466
Bernard Avatar asked Mar 27 '17 04:03

Bernard


1 Answers

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:

  • new technologies: just moved to Stage 4 at January's TC39 meeting (in time for ES2017)
  • won't reliably run on older browsers (older versions have a different API, or no implementation available)
  • esoteric hack (similar to the C++ memory model)
  • works

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.

like image 69
JF Bastien Avatar answered Oct 13 '22 00:10

JF Bastien