Search code examples
javascriptmultithreadinggoogle-chromexmlhttprequestweb-worker

Web Worker blocked by main thread in Chrome


I have a Web Worker. I wish to make periodic network requests with it. One thing I particularly want is to make these requests even if the main JS execution thread is blocked (eg by a window.alert). I'm using Chrome 38.

However, when I attempt to make network requests in the worker, the requests appear to be blocked by the UI thread. Here is a contrived example to illustrate the problem:

base.js:

var worker = new Worker("/worker.js");

setTimeout(function() {
    console.log("begin blocking");
    var startDt = new Date();
    var blockPeriod = 5000;
    var a;
    // Obviously we'd never actually do this, but this while loop
    // is a convenient way to create the problem case (a blocked main
    // thread).
    while ((new Date() - startDt) < blockPeriod) {
        a = 0;
    }
    console.log("stop blocking");
}, 3000);

worker.js:

var requestInterval = 1000;

var sendRequest = function() {
    console.log("Send request interval");

    var request = new XMLHttpRequest();
    request.open("GET", "/ping", true);

    request.onload = function() {
        if (request.status === 200){
            console.log(request.responseText)
        } else {
            console.log(request.status)
        }
    };

    request.onerror = function() {
        console.log("error")
    };

    request.send();

    setTimeout(sendRequest, requestInterval);
}

sendRequest();

The result I'm seeing is that we see successful HTTP requests for three seconds, until the blocking begins. At this point, we don't see anything logged to the console until the blocking ends, at which point we see five "Send request interval"s followed by 5 logs of the response, like so:

Send request interval
{"pong": true}
Send request interval 
{"pong": true} 
Send request interval
{"pong": true}
Send request interval
{"pong": true}
begin blocking
stop blocking
5x Send request interval
5x {"pong": true}
Send request interval
{"pong": true}

I also see in my server logs that no requests are made in that blocking time, then those five requests are all received roughly simultaneously at the end of the blocking period.

Given that "Send request interval" occurs five times in a row, the worker is evidently continuing to execute: if it weren't, it wouldn't make it through to queue up the next iteration. I've also found that if I block by triggering a window.alert instead of spinning in a loop, I get the log messages from the beginning of sendRequest at 1 second intervals, and then get the response handler log messages in a large batch as soon as I stop blocking.

In Firefox, the background thread seems to stop entirely in this case (I don't get that same batch of five requests queued up during the blocked period). However, I'm only targeting Chrome in this case (and I ultimately want to use WebSockets which don't even work in Firefox Workers), so I'm not really interested in that.

All added together, this leads me to believe that there are some classes of activity in Web Workers which are blocked by the spawning thread, and some which are not (I originally saw this same behavior with WebSockets). Concretely, I'd like to know (if anyone does know):

  1. What Worker activity is blocked by the main thread in Chrome?
  2. Is there a way to work around this? I'd very much like to be able to able to establish a WebSocket connection in a Worker, and then continue to PING/PONG back and forth, even if something (such as an alert/confirm) does block the main thread.
  3. Is this all nonsense, and am I just doing something stupid?

Solution

  • Your observation is correct. When the UI thread is blocked, network calls aren't dispatched.

    Even worse, Chrome has the best behavior of the bunch. When a worker makes a XHR request when the UI thread is blocked:

    • Chrome: all requests are queued. The browser will not actually issue the requests until the UI thread unblocks. On the plus side, the worker thread is still free to run.
    • Firefox: new XMLHttpRequest() blocks until the UI thread unblocks.
    • IE: xhr.open() blocks until the UI thread unblocks.

    While Chrome fortunately does not cause a worker thread to stop and wait (even though it won't get any data), Firefox and IE will cause a worker thread to wait on the UI thread when you try to make a XHR request.

    There is no way to work around this; you're beholden to the browser to make requests on your behalf. I haven't done any testing with WebSockets, but they may deliver events even if the UI thread is blocked. At worst, the received messages would queue until the UI thread unblocks.