Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

The JavaScript Event Loop and Web Workers

So I've been having a long talk with a colleague regarding the JavaScript event loop and the use of Web Workers. In a single Web page, different Web Workers have different stacks, heaps, and message queues, form here, specifically:

A web worker or a cross-origin iframe has its own stack, heap, and message
queue. Two distinct runtimes can only communicate through sending messages via
the postMessage method. This method adds a message to the other runtime if the
latter listens to message events.

but are all the messages executed inside the same event loop, or does each Web Worker have its own event loop?

I'm asking this because I have two Web Workers in a page, one executes a very computationally-heavy operation in sequence, while the other just handles a WebRTC connection. I will not go into details but it seems to me that the computationally-heavy Web Worker is taking away so much computational time out of the JavaScript event loop that the other Worker, that only has to keep the connection alive (through heartbeat I suppose) isn't able to do so, and the connection is eventually lost.

This is what I believe. If that is not the case, and the two Web Workers work on different event loops then I cannot explain why the connection is lost when the load on the computing Web Worker is heavy (when the load is light then the connection is not lost).

like image 358
Masiar Avatar asked Dec 13 '16 15:12

Masiar


1 Answers

Each worker has its own event loop. From the specification:

Each WorkerGlobalScope object has a distinct event loop, separate from those used by units of related similar-origin browsing contexts.

and then here:

The global scope is the "inside" of a worker.

...which is followed by the definition of the WorkerGlobalScope interface referenced in the earlier quote.

Your computation-heavy worker might be dominating the available processing time, but it isn't blocking the other worker's event loop.

We can also readily check this with a quick test:

page.html:

<!DOCTYPE HTML "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta charset="UTF-8">
<title>Two Workers</title>
<style>
body {
    font-family: sans-serif;
}
pre {
    margin: 0;
    padding: 0;
}
</style>
</head>
<body>
<div>Fast: <span id="fast"></span></div>
<div>Slow: <span id="slow"></span></div>
<script>
(function() {
    var fastWorker = new Worker("fastworker.js");
    var fast = document.getElementById("fast");
    var slowWorker = new Worker("slowworker.js");
    var slow = document.getElementById("slow");

    fastWorker.addEventListener("message", function(e) {
        fast.innerHTML = e.data || "??";
        fastWorker.postMessage("ping");
    });

    slowWorker.addEventListener("message", function(e) {
        slow.innerHTML = e.data || "??";
        slowWorker.postMessage("ping");
    });

    fastWorker.postMessage("start");
    slowWorker.postMessage("start");
})();
</script>
</body>
</html>

slowworker.js:

var counter = 0;
self.addEventListener("message", function(e) {
    var done = Date.now() + 1000; // 1 second
    while (Date.now() < done) {
        // Busy wait (boo!)
    }
    ++counter;
    self.postMessage(counter);
});

fastworker.js:

var counter = 0;
self.addEventListener("message", function(e) {
    var done = Date.now() + 100; // 100ms
    while (Date.now() < done) {
        // Busy wait (boo!)
    }
    ++counter;
    self.postMessage(counter);
});

As you can see, "fast"'s number goes up much more quickly than "slow", showing it's processing its messages.

(I could have made one worker file and sent the delay in the start command, but...)

like image 161
T.J. Crowder Avatar answered Oct 14 '22 00:10

T.J. Crowder