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);