7

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?

Community
  • 1
  • 1
Bernard
  • 5,209
  • 1
  • 34
  • 64
  • the title asks to obtain data synchronously FROM WebWorker ... yet the body of the question is asking how WebWorker can obtain data FROM so-called main - which is it? – Jaromanda X Mar 27 '17 at 04:48
  • I don't think there is a problem with the phrasing of this question. The meaning of "from" depends on whether "WebWorker" is used as a place or a person/object. The title uses "WebWorker" as a place, e.g. "look from a distance" or "participate in a contest from home", meaning that code located in a WebWorker is trying to get something done (in this case, obtain data). You are using "WebWorker" as an object, e.g. "take cookies from the jar", where the agent/object has the things you want to obtain. I could have written "Obtain WebWorker data synchronously?" if I wanted to ask your question. – Bernard Mar 27 '17 at 05:59
  • right ... thanks for the lesson ... don't do asynchronous tasks synchronously – Jaromanda X Mar 27 '17 at 07:27

1 Answers1

12

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.

JF Bastien
  • 6,673
  • 2
  • 26
  • 34
  • This looks promising. And it's even newer than WebAssembly whose MVP was released less than a month ago. – Bernard Mar 27 '17 at 10:06
  • I guess I'm not sure why blocking the main thread is necessarily a problem. If you're using the worker in such a way that it's always part of a sequence like 1) user interaction, 2) app requests something from the worker, 3) worker returns something to the app, 3) the UI updates. In that case, the code can most easily be written in a synchronous style and being forced to do it asynchronously might massively complicate things. – Devin Dec 22 '21 at 15:52