0

This issue arose deep in a huge and complex application, but it is possible to reproduce in a trivial use case: if the main browser thread is busy, an XHR sent by a web worker will block until the main browser thread is free, almost as if the worker were a normal function hung off a 0ms timeout, waiting for its turn on the main thread. That isn't actually what's happening: there is concurrency going on, we can tell that from the timings of log entries, but it seems like the actual sending of the XMLHttpRequest can only be done when the main Javascript thread is free.

At first, we thought this behavior was limited to Internet Explorer, but in fact it only appears that way because IE is much slower in our particular use case, so the main browser thread stays busy for longer. This non-concurrent behavior is actually present in all the browsers I have tried (IE, Chrome, Firefox). The browsers don't behave exactly the same: sometime they block on the open() call, sometimes they block on the send() and sometimes they claim that send() completed in a timely fashion, but using an HTTP debugger like Fiddler shows that the data didn't actually get sent to the server until the main browser thread finished its long-running task.

So I'm hoping somebody can tell me why this is, and if there is any way to make a web worker send an XHR in a properly concurrent fashion. BTW, I already know that the crux of the overall problem is how busy the main Javascript thread is, so please don't tell me to fix that; we are working on that as well, but the long-running task in question is a reflow of an extremely complicated JS-based UI in a customer's application, so we are limited in what we can do there.

This repro case shows the problem:

<html>
<body>
<div id="test"></div>
<script>
worker = new Worker('worker.js');
// Wait for 2 seconds to be as sure as we can be that it has downloaded and installed the worker code

setTimeout(proceed, 2000);

function proceed() {

    worker.postMessage("");
    console.log("Message sent to worker: " + new Date().getTime());

    // Now tie up the JS thread
    setTimeout(function() {
        console.log("Started crazy pointless loop: " + new Date().getTime());
        for (var i = 0; i < 500000; i++) {
            var j = i / 17,
                k = j * j,
                l = k / 123;
            document.getElementById("test").innerHTML = "l = " + l; 
        }

        console.log("Finished crazy pointless loop: " + new Date().getTime());
    }, 0);
}
</script>
</body>
</html>

and the worker.js file:

addEventListener('message', function(e) {

    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
        if (xhr.readyState < 4) return;
        console.log("Response received: " + new Date().getTime());
    }
    console.log("Ready to open XHR: " + new Date().getTime());
    xhr.open("GET", "dummy.html?nocache=" + new Date().getTime(), true);
    console.log("XHR opened: " + new Date().getTime());
    xhr.send();
    console.log("XHR sent: " + new Date().getTime());

}, false);
  • I don't have any data to support this, so this isn't an answer. But I strongly suspect all browsers use the main thread to limit the # of concurrent connections to a given domain. HTTP 1.1 specifies not more than 2 to any given domain. While most browsers don't respect _that low of a limit_, they all do have an upper bound that I expect is enforced via the main thread. For more info, see http://stackoverflow.com/questions/985431/max-parallel-http-connections-in-a-browser . – Peter Wagener Oct 19 '16 at 17:20

1 Answers1

1

You'd be better off sending the XHR synchronously from the worker since you're already operating in a separate thread:

addEventListener('message', function(e) {
    var xhr = new XMLHttpRequest();
    console.log("Ready to open XHR: " + new Date().getTime());
    xhr.open("GET", "dummy.html?nocache=" + new Date().getTime(), false); // false makes it synchronous
    console.log("XHR opened: " + new Date().getTime());
    xhr.send();
    console.log("Response received: " + new Date().getTime());
}, false);
idbehold
  • 16,833
  • 5
  • 47
  • 74