Web workers terminating abruptly - javascript

I initiated a web worker on chrome and it had a simple function that was called repeatedly using setTimeout. Surprisingly the web worker terminated after the function was called around 1000 times. Can anyone explain why? I guess chrome is doing some optimization.
webworker.js
function hi() {
postMessage('1');
setTimeout(hi, 1);
}
hi();
main.js
var blob = new Blob([code]);
var blobURL = window.URL.createObjectURL(blob);
var worker = new Worker(blobURL);
worker.onmessage = function(data) {
console.log(data.data); // gets called around 1000 times and done
};
EDIT:
Reproduced in a fiddle:
http://jsfiddle.net/meovfpv3/1/
It seems to takes arbitrarily long for the onmessage callback to stop firing, as quickly as a few seconds and as long as +5 mins

Here is my best guess at what is happening. By posting a message from the Web Worker every 1ms, you are demanding that the main thread processes each posted message within 1ms.
If the main thread isn't able to process the message within 1ms, you are still sending it a new message even though it isn't finished processing the last message. I would imagine this puts it into a queue of messages waiting to be processed.
Now since you are sending messages from the web worker faster than they can be processed, this queue of unprocessed messages is going to get bigger and bigger. At some point Chrome is going to throw up its hands and say "There are too many messages in the queue", and instead of queueing new messages for processing, it drops them.
This is why if you use a reasonable number in your timeout like 100ms, the message has plenty of time to be processed before the next message is sent, and no problem with unprocessed messages occurs.
I've created a jsFiddle where the worker sends a message to the main thread, and the main thread sends the message back to the worker. If that process doesn't happen before the next message is sent, the counters in both threads will be mismatched and the web worker will terminate.
http://jsfiddle.net/meovfpv3/3/
You can see that with a reasonable setTimeout of 100ms, all messages have adequate time to process before the next message occurs.
When you lower the setTimeout to 1ms, the message chain doesn't have time to finish before the next message is sent and the counters in each thread become eventually desynced, tripping the if clause and terminating the web worker.
One way to fix this problem is instead of blindly posting a message every 1ms whether the last one has been processed or not, only post a new message after you have received a message back from the main thread. This means that you are only posting messages as fast as the main thread can process them.
For completeness here is a copy of the JSFiddle code:
Worker:
var counter2 = 0;
var rcvd = true;
function hi() {
counter2++;
console.log("")
console.log("postMessage", counter2)
postMessage(counter2);
if (!rcvd) {
self.close();
console.log("No message received");
}
rcvd = false;
setTimeout(hi, 1);
}
hi();
onmessage = function(e) {
rcvd = true;
console.log("secondMessage", e.data);
}
Main:
var ww = document.querySelector('script[type="text/ww"]'),
code = ww.textContent,
blob = new Blob([code], {type: 'text/javascript'}),
blobUrl = URL.createObjectURL(blob),
worker = new Worker(blobUrl),
counter = 0;
worker.onmessage = function(e) {
counter++;
console.log("onmessage:", counter);
worker.postMessage(e.data);
}

Firstly, a couple of observations, which I cannot explain but are kind of interesting and might be inspirational for someone:
#Anson - If I put your jsFiddle code into Codepen (still in Chrome) there are no problems there. The onmessage callback just keeps working!
And back in jsFiddle... It fails even changing the setTimeout to a long gap like 10s so it's not the number of times that the worker posts a message, it's how long before the onmessage callback stops firing – which has a lot of variance.
Then I found some ways to keep the onmessage handler alive in this specific example:
Add a button/link in the html and a handler (I used jQuery) that will terminate the worker on click. Just adding this code fixes it. $("#stop").on("click",function(e){e.preventDefault();worker.terminate();});
Just add console.log(worker) after defining onmessage.
Inspired by an answer posted in the related question you can also simply add window.worker = worker after defining onmessage.
Something about mentioning worker again in all cases seems to keep it alive.

Are you trying to postMessage every 1ms? Then you probably meant to use setInterval():
setInterval(function(){
postMessage('1');
}, 1);
Edit: I incorrectly saw recursion which wasn't there, just because I was looking for it. I would still use setInterval over setTimeout though.

Related

Worker.onmessage firing synchronously?

Investigating a strange bug for which the stack trace (on Firefox 87) points to the line where the onmesssage handler is assigned:
const worker = new Worker(/* URI */);
worker.onmessage = msg => { // stacktrace points here
let trx = JSON.parse(msg.data);
// ...
}
// more init code
The calls in the stacktrace leading into this spot match the context in which the worker is created and onmessage assigned, but the calls after this spot in the stack trace make it seem like the handler function is called synchronously when assigned.
The worker itself connects to the server and can push messages to the main thread, without the main thread first having to post messages to it. It's thus entirely possible for a new message to be there before the onmessage assignment is executed. However, I've been unable to reproduce the behavior. It seems messages posted before the handler is assigned are discarded.
Short of a race condition in Firefox itself, is there anything else that could be going on?
(The race in question here is different from this similar question)

How to cancel a wasm process from within a webworker

I have a wasm process (compiled from c++) that processes data inside a web application. Let's say the necessary code looks like this:
std::vector<JSONObject> data
for (size_t i = 0; i < data.size(); i++)
{
process_data(data[i]);
if (i % 1000 == 0) {
bool is_cancelled = check_if_cancelled();
if (is_cancelled) {
break;
}
}
}
This code basically "runs/processes a query" similar to a SQL query interface:
However, queries may take several minutes to run/process and at any given time the user may cancel their query. The cancellation process would occur in the normal javascript/web application, outside of the service Worker running the wasm.
My question then is what would be an example of how we could know that the user has clicked the 'cancel' button and communicate it to the wasm process so that knows the process has been cancelled so it can exit? Using the worker.terminate() is not an option, as we need to keep all the loaded data for that worker and cannot just kill that worker (it needs to stay alive with its stored data, so another query can be run...).
What would be an example way to communicate here between the javascript and worker/wasm/c++ application so that we can know when to exit, and how to do it properly?
Additionally, let us suppose a typical query takes 60s to run and processes 500MB of data in-browser using cpp/wasm.
Update: I think there are the following possible solutions here based on some research (and the initial answers/comments below) with some feedback on them:
Use two workers, with one worker storing the data and another worker processing the data. In this way the processing-worker can be terminated, and the data will always remain. Feasible? Not really, as it would take way too much time to copy over ~ 500MB of data to the webworker whenever it starts. This could have been done (previously) using SharedArrayBuffer, but its support is now quite limited/nonexistent due to some security concerns. Too bad, as this seems like by far the best solution if it were supported...
Use a single worker using Emterpreter and using emscripten_sleep_with_yield. Feasible? No, destroys performance when using Emterpreter (mentioned in the docs above), and slows down all queries by about 4-6x.
Always run a second worker and in the UI just display the most recent. Feasible? No, would probably run into quite a few OOM errors if it's not a shared data structure and the data size is 500MB x 2 = 1GB (500MB seems to be a large though acceptable size when running in a modern desktop browser/computer).
Use an API call to a server to store the status and check whether the query is cancelled or not. Feasible? Yes, though it seems quite heavy-handed to long-poll with network requests every second from every running query.
Use an incremental-parsing approach where only a row at a time is parsed. Feasible? Yes, but also would require a tremendous amount of re-writing the parsing functions so that every function supports this (the actual data parsing is handled in several functions -- filter, search, calculate, group by, sort, etc. etc.
Use IndexedDB and store the state in javascript. Allocate a chunk of memory in WASM, then return its pointer to JavaScript. Then read database there and fill the pointer. Then process your data in C++. Feasible? Not sure, though this seems like the best solution if it can be implemented.
[Anything else?]
In the bounty then I was wondering three things:
If the above six analyses seem generally valid?
Are there other (perhaps better) approaches I'm missing?
Would anyone be able to show a very basic example of doing #6 -- seems like that would be the best solution if it's possible and works cross-browser.
For Chrome (only) you may use shared memory (shared buffer as memory). And raise a flag in memory when you want to halt. Not a big fan of this solution (is complex and is supported only in chrome). It also depends on how your query works, and if there are places where the lengthy query can check the flag.
Instead you should probably call the c++ function multiple times (e.g. for each query) and check if you should halt after each call (just send a message to the worker to halt).
What I mean by multiple time is make the query in stages (multiple function cals for a single query). It may not be applicable in your case.
Regardless, AFAIK there is no way to send a signal to a Webassembly execution (e.g. Linux kill). Therefore, you'll have to wait for the operation to finish in order to complete the cancellation.
I'm attaching a code snippet that may explain this idea.
worker.js:
... init webassembly
onmessage = function(q) {
// query received from main thread.
const result = ... call webassembly(q);
postMessage(result);
}
main.js:
const worker = new Worker("worker.js");
const cancel = false;
const processing = false;
worker.onmessage(function(r) {
// when worker has finished processing the query.
// r is the results of the processing.
processing = false;
if (cancel === true) {
// processing is done, but result is not required.
// instead of showing the results, update that the query was canceled.
cancel = false;
... update UI "cancled".
return;
}
... update UI "results r".
}
function onCancel() {
// Occurs when user clicks on the cancel button.
if (cancel) {
// sanity test - prevent this in UI.
throw "already cancelling";
}
cancel = true;
... update UI "canceling".
}
function onQuery(q) {
if (processing === true) {
// sanity test - prevent this in UI.
throw "already processing";
}
processing = true;
// Send the query to the worker.
// When the worker receives the message it will process the query via webassembly.
worker.postMessage(q);
}
An idea from user experience perspective:
You may create ~two workers. This will take twice the memory, but will allow you to "cancel" "immediately" once. (it will just mean that in the backend the 2nd worker will run the next query, and when the 1st finishes the cancellation, cancellation will again become immediate).
Shared Thread
Since the worker and the C++ function that it called share the same thread, the worker will also be blocked until the C++ loop is finished, and won't be able to handle any incoming messages. I think the a solid option would minimize the amount of time that the thread is blocked by instead initializing one iteration at a time from the main application.
It would look something like this.
main.js -> worker.js -> C++ function -> worker.js -> main.js
Breaking up the Loop
Below, C++ has a variable initialized at 0, which will be incremented at each loop iteration and stored in memory.
C++ function then performs one iteration of the loop, increments the variable to keep track of loop position, and immediately breaks.
int x;
x = 0; // initialized counter at 0
std::vector<JSONObject> data
for (size_t i = x; i < data.size(); i++)
{
process_data(data[i]);
x++ // increment counter
break; // stop function until told to iterate again starting at x
}
Then you should be able to post a message to the web worker, which then sends a message to main.js that the thread is no longer blocked.
Canceling the Operation
From this point, main.js knows that the web worker thread is no longer blocked, and can decide whether or not to tell the web worker to execute the C++ function again (with the C++ variable keeping track of the loop increment in memory.)
let continueOperation = true
// here you can set to false at any time since the thread is not blocked here
worker.expensiveThreadBlockingFunction()
// results in one iteration of the loop being iterated until message is received below
worker.onmessage = function(e) {
if (continueOperation) {
worker.expensiveThreadBlockingFunction()
// execute worker function again, ultimately continuing the increment in C++
} {
return false
// or send message to worker to reset C++ counter to prepare for next execution
}
}
Continuing the Operation
Assuming all is well, and the user has not cancelled the operation, the loop should continue until finished. Keep in mind you should also send a distinct message for whether the loop has completed, or needs to continue, so you don't keep blocking the worker thread.

Posting a message to a web worker while it is still running

Say we have a web worker referring to a file called "worker.js". We use the worker to execute a function in "worker.js" that does some lengthy operation. We call post the respective message to the worker and proceed in the main thread. However, before the worker has finished doing its initial work, the main thread posts another message to it.
My question: Will the worker continue with our time-taking function and only process the newly posted message once finished or will it interrupt its current operation until the new one has been completed?
I've tried out the following code in Google Chrome's debugger:
worker.js:
var cosine;
self.onmessage = function(e) {
if (e.data.message == 0) {
for (var i = 0; i < 10000000; i++) {
cosine = Math.cos(Math.random());
if (i % 1000 == 0) console.log("hello world");
}
} else if (e.data.message == 1) {
console.log("xyz");
}
};
main.js:
var worker;
function main() {
worker = new Worker("js/worker.js");
worker.postMessage({message: 0});
setTimeout(xyz, 10);
}
function xyz() {
worker.postMessage({message: 1});
}
output:
(10000 times) test.js:11 hello world
test.js:14 xyz
The cosine variable is recalculated with every new iteration to provide the "time-taking" algorithm described in the question. Apparently, the message is only received as soon as the last operation has finished, as I observed the "xyz" output being printed immediately after the 10000th "hello world" output.
A Web Worker is backed by a single thread. This means that while the "onmessage" handler is being executed, it will not be able to receive another "onmessage" event until the previous one has finished.
This is quite inconvenient if we want to implement some background computation process that we want to be able to pause and resume, because there is no way to send a "pause" message to a running worker. We are able to stop a web worker via worker.terminate(), but that completely kills the worker.
The only way I can think of is by slicing worker execution into chunks, then post a message from the worker at the end of each chunk, which then triggers a message to the worker to process the next chunk, and so on.

How to let javascript main thread sleep?

I'm writing a firefox addon and I want to do something before every http request is issued. Pseudocode:
observerService.addObserver(httpRequestObserver, "http-on-modify-request", false);
var httpRequestObserver=
{
observe: function()
{
var httpChannel = subject.QueryInterface(Ci.nsIHttpChannel);
asyncFunction1(){
// asynchronous function No.1
}.then(asyncFunction2(){
// asynchronous function No.2, this function will be called after asyncFunction1 finished
// do something to httpChannel (edit the request header etc.)
});
//point 1
}
}
The problem is that asyncFunction2() may finishes after observe() finished. And according to Firefox, the request will be issued after observe() finished. Thus when asyncFunction2() is editing httpChannel, the variable 'httpChannel' is already out of date(because observe() ends).
To keep the request not issued before asyncFunction2() finished, I need to do sth to let the main thread wait for asyncFunction2() at point 1. I have tried to put 'setTimeout(function waitfor(){},xxxx)' at point 1, but waitfor() starts after observe() ends. I also tried to put asyncFunction1&2 in a chrome workder(similar to web worker) and let the worker thread sends a message when asyncFunction2 finished. However, javascript seems unable to be interrupted when executing the main thread. What i found is that javascript only put the 'receive a message ! event' into the task queue. Thus when javascript is dealing with the message, the observe() has already returned.

setTimeout from a web worker

What if I want to put a web worker on pause if I cannot proceed processing data, and try a second later? Can I do that in this manner inside a web worker?
var doStuff = function() {
if( databaseBusy() ) {
setTimeout(doStuff, 1000);
} else {
doStuffIndeed();
}
}
I'm getting Uncaught RangeError: Maximum call stack size exceeded and something tells me it's because of the code above.
If by "pause" you mean "further calls to worker.postMessage() will get queued up and not processed by the worker", then no, you cannot use setTimeout() for this. setTimeout() is not a busywait, but rather an in-thread mechanism for delaying work until a later scheduled time. The web worker will still receive a new onmessage event from the main queue as soon as it is posted.
What you can do, however, is queue them up manually and use setTimeout to try to process them later. For example:
worker.js
var workQueue = [];
addEventListener('message',function(evt){
workQueue.push(evt.data);
doStuff();
},false);
function doStuff(){
while (!databaseBusy()) doStuffIndeed(workQueue.shift());
if (workQueue.length) setTimeout(doStuff,1000);
}
Each time a message is received the worker puts it into a queue and calls tryToProcess.
tryToProcess pulls messages from the queue one at a time as long as the database is available.
If the database isn't available and there are messages left, it tries again in 1 second.

Categories