I'm learning Web Workers in javascript, using VSCode. My worker sets up an interval during its onmessage event. Ideally calling start will only ever create the interval if it isn't already running.
onmessage = (event: MessageEvent) => {
let interval;
switch (event.data.command){
case "start":
if (!interval){
interval = setInterval(myFunction, 1000);
}
break;
case "stop":
if (interval){
clearInterval(interval);
interval = null;
}
break;
}
}
The problem is that interval becomes defined within the execution of the function, so I could start multiple intervals without any of them knowing about one another.
const worker = new Worker("myworker.ts");
worker.postmessage({command: "start"});
worker.postmessage({command: "start"});
worker.postmessage({command: "start"});
If I modify the web worker code to use a global variable, like this, then according to VSCode other worker files will have access to the interval variable:
let interval; // Error: `interval` is already defined in `anotherWorker.ts`
onmessage = () => {
// ...
if (!interval){
interval = setInterval(myFunction, 1000);
}
// ...
if (interval){
clearInterval(interval);
interval = null;
}
}
Even if each worker gets its own scope, VSCode will continue to throw errors if I reuse variable names across workers.
How can I create a local state for the worker onmessage function?
You can wrap your onmessage handler in its own block, and declare your interval there. This way, other modules won't see it, but it will still persist across different message events.
{ // <- defines a new block
let interval; // <- only for us, but still the same for all message events
onmessage = () => {
// ...
if (!interval){
interval = setInterval(myFunction, 1000);
}
// ...
if (interval){
clearInterval(interval);
interval = null;
}
};
}
I recently ran into problems when taking an approach like #Kaiido's answer in a service worker. Browsers may restart service workers (and possibly other workers?) whenever they see fit, so you can lose state stored in variables within the worker.
In my case, I created a service worker to return all instances of an app to the login page if the user is inactive. This would sometimes fail to occur due to the worker being restarted by the browser and losing the timeout I had set.
If you want to maintain the state across service worker restarts, you should store the state in IndexedDB and re-initialize any worker-localized state whenever it is reactivated.
For things like timeouts and intervals which require the worker to remain alive you might strongly consider moving that functionality into the client app, where reasonable. Otherwise, you might consider periodically sending keep-alive messages at the largest granularity you can tolerate to ensure it will be reactivated at that interval and have a chance to act on a timeout (via timestamps stored in IndexedDB) that should have triggered while it slept.
Related
When I look at tutorials/documentation about WebSockets I find code like this:
var ws = new WebSocket("ws://localhost:8765/dlt");
ws.onopen = () => {
// do some very important stuff after connection has been established
console.log("onopen");
}
But what about race conditions here? Are there somehow avoided in JavaScript?
For example this code (which just assigns onopen after the connection has been opened) will fail:
var ws = new WebSocket("ws://localhost:8765/dlt");
setTimeout(() => {
ws.onopen = () => {
// do some very important stuff after connection has been established
console.log("onopen"); /// <== won't be called
}
}, 100);
Can I be sure that the assignment has been done before the connection get's established?
(I tried to extend WebSocket with a custom onopen() method but this doesn't seem to work)
class MyWebSocket extends WebSocket {
onopen() {
console.log("onopen()");
/// do some very important stuff after connection has been established
}
}
You should have a read about javascript's event loop: https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#Event_loop
If you look at the section about Run-to-completion, you get this useful explanation:
Each message is processed completely before any other message is processed. This offers some nice properties when reasoning about your program, including the fact that whenever a function runs, it cannot be pre-empted and will run entirely before any other code runs (and can modify data the function manipulates). This differs from C, for instance, where if a function runs in a thread, it may be stopped at any point by the runtime system to run some other code in another thread.
So in your example, the assignment to ws.onopen must be completed before the websocket does anything asynchronous in nature. By putting your assignment inside setTimeout, you are moving it outside of the currently running context, and so it may not be executed before it is required by the websocket.
You should rest assured that the example is ok. The Javascript event loop will finish the current task before assuming any other tasks. This means that 1) the WebSocket cannot open the connection (async operation) before the onopen event, 2) the onopen event handler will be called during the following cycles.
Setting the timeout on the other hand will complicate matters, because the events will be called in some order after the current task. This means that that the WebSocket has chance to open the connection before the handler has been set.
I'm trying to get a handle on web workers when I came across a very peculior behaviour. For some reason it's terminated after a few seconds, even though I have code in it that's running.
Here's my code;
Main JavaScript-file:
$(document).ready(function () {
var worker = new Worker("js/TestWorker.js");
worker.addEventListener('message', function (event) {
console.log(event.data);
});
worker.addEventListener('error', function (event) {
console.log(event);
});
});
Worker file:
(function () {
var updateCounter = 0;
var updater = function () {
updateCounter += 1;
console.log("Update counter: " + updateCounter);
postMessage("test");
setTimeout(updater, 10000);
};
updater();
})();
As stated, the worker just stops functioning after a few seconds, 10-20seconds or so.
But if I add this piece of code to my main JavaScript-file;
var check = function () {
var localWorker = worker;
// setTimeout(check, 1000);
};
// setTimeout(check, 1000);
The worker works as intended. The setTimeout-calls aren't needed either, hence why they're commented out. (Note that I can just aswell replace the assignment with worker.length or something similar and it will still work just fine.
Can someone explain this behaviour? Is the worker getting terminated and (erroneously) garbage-collected by the browser or is something else happening here that I'm missing?
Worth to note is that my browser (Chrome) isn't outputing any errors or warnings to the console either.
EDIT: The same behaviour is observed whether the code is executed inside an anonymous function or not.
EDIT2: If I place the worker variable in the global scope it does not get terminated prematurely. What might be the reason for this?
Some research shows that while web workers are supposed to function as you expect (i.e. won't be perceptibly garbage collected), there are some known issues in Chrome which mean you can't rely on that behaviour.
Of specific interest would be this very similar bug: https://bugs.chromium.org/p/chromium/issues/detail?id=572225 which in turn references a more underlying bug: https://bugs.chromium.org/p/chromium/issues/detail?id=572226
It seems to be due to an attempt to garbage collect workers which cannot possibly perform any future activities (in which case the garbage collection would be undetectable, as it's supposed to be), but a flaw in the logic for detecting this state means that any pending activities which aren't directly related to responding to an incoming message will be ignored.
Basically, while you should be able to assume web-workers behave like DOM nodes which can only be removed explicitly, in practice (for now) you need to make sure you always keep a reference to the worker accessible from somewhere, otherwise when the garbage collector kicks in it may kill the worker. This is only necessary if you're using setTimeout or similar in the worker; if it just responds to messages, you won't have a problem.
Maybe worker var must be global
var worker;
$(document).ready(function () {
worker = new Worker("js/TestWorker.js");
worker.addEventListener('message', function (event) {
console.log(event.data);
});
worker.addEventListener('error', function (event) {
console.log(event);
});
});
(function () {
...
})();
This is a anonymous function which will be called once after definition and after that the browser throws it away.
Your web worker is defined it that scope and that's why it's only working for a short period of time.
I am developing a web application in node.js to collect data from devices on a network using snmp. This is my first real encounter with node.js and javascript. In the app each device will be manipulated through a module I named SnmpMonitor.js. This module will maintain basic device data as well as the snmp and database connection.
One of the features of the app is the ability to constantly monitor data from smart metering devices. To do this I created the following code to start and stop the monitoring of the device. It uses setInterval to constantly send a snmp get request to the device. Then the event listener picks it up and will add the collected data to a database. Right now the listener just prints to show it was successful.
var dataOIDs = ["1.3.6.1.2.1.1.1.0","1.3.6.1.2.1.1.2.0"];
var intervalDuration = 500;
var monitorIntervalID;
var dataCollectionEvent = "dataCollectionComplete";
var emitter = events.EventEmitter(); // Uses native Event Module
//...
function startMonitor(){
if(monitorIntervalID !== undefined){
console.log("Device monitor has already started");
} else {
monitorIntervalID = setInterval(getSnmp,intervalDuration,dataOIDs,dataCollectionEvent);
emitter.on(dataCollectionEvent,dataCallback);
}
}
function dataCallback(recievedData){
// receivedData is returned from getSnmp completion event
// TODO put data in database
console.log("Event happened");
}
function stopMonitor(){
if(monitorIntervalID !== undefined){
clearInterval(monitorIntervalID);
emitter.removeListener(dataCollectionEvent,dataCallback);
} else {
console.log("Must start collecting data before it can be stopped");
}
}
//...
I also have a test file, test.js, that requires the module, starts monitoring, waits 10 seconds, then stops it.
var test = require("./SnmpMonitor");
test.startMonitor();
setTimeout(test.stopMonitor,10000);
My problem is that the setInterval function in startMonitor() is not being run. I have tried placing console.log("test"); before, inside, and after it to test it. The inside test output never executes. The monitorIntervalID variable is also returned as undefined. I have tested setInterval(function(){ console.log("test"); },500); in my test.js file and it runs fine with no issues. I feel like this is a noobie mistake but I just can't seem to figure out why it won't execute.
Here is a link to the entire module: SnmpMonitor.js
I not sure exactly what was wrong but I got it to work by overhauling the whole class/module. I thought the way I had it was going to allow me to create new monitors objects but I was wrong. Instead I created two functions inside the monitor file that do the same thing. I changed the start function to the following.
SnmpMonitor.prototype.start = function() {
var snmpSession = new SNMP(this.deviceInfo.ipaddress,this.emitter);
var oids = this.deviceInfo.oids;
var emit = this.emitter;
var duration = this.intervalDuration;
this.intervalID = setInterval(function(){
snmpSession.get(dataCollectionEvent,emit,oids);
},duration);
};
The setInterval function seems to work best when the callback function is set inside an anonymous function, even though technically you can pass it directly. Using the this. notation I created some class/module/function variables (whatever its called in js) that are in scope of the whole class. For some reason the variables accessed through this. do not work so well when directly in a function or expression so I created temp variables for them. In my other version all the variables were global and js doesn't seem to like that.
I'm reloading a module this way:
require('./module.js'); // require the module
delete require.cache('/module.js'); // delete module from cache
require('/module.js'); // re-require the module
But there is a problem if the module contains something like this:
setInterval(function(){
console.log('hello!');
}, 1000);
Every time I reload the module a new setInterval is called, but the last one is NOT closed.
Is there any way to know about each module's (long) running functions so I can stop them before I require it again? Or any suggestions how can I make this work?
I'm open to any crazy ideas.
This is just a wild guess but you may be able to load the module within a domain.
When you are done use domain.dispose() to clear the timers:
The dispose method destroys a domain, and makes a best effort attempt
to clean up any and all IO that is associated with the domain. Streams
are aborted, ended, closed, and/or destroyed. Timers are cleared.
Explicitly bound callbacks are no longer called. Any error events that
are raised as a result of this are ignored.
I would simply set a reference to the interval and expose a method in order to stop it like:
var interval = setInterval(function () {
console.log('hello');
}, 1000);
var clearInt = clearInterval(interval);
I dont think you can hook into any events as you are simply deleting a reference. If it doesnt exist anymore it reloads. Before you do this call the clearInt function.
You could create an IntervalRegistry in your main application:
global.IntervalRegistry = {
intervals : {},
register : function(module, id) {
if (! this.intervals[module])
{
this.intervals[module] = [];
}
this.intervals[module].push(id);
},
clean : function(module) {
for (var i in this.intervals[module])
{
var id = this.intervals[module][i];
clearInterval(id);
}
delete this.intervals[module];
}
};
In your module, you would register the interval created there:
// module.js
IntervalRegistry.register(__filename, setInterval(function() {
console.log('hello!');
}, 1000));
When it's time to clean up, call this:
var modulename = '/full/path/to/module.js'; // !!! see below
IntervalRegistry.clean(modulename);
delete require.cache[modulename];
Remember that modules are stored with their full filename in require.cache.
I am not too familiar with the specifics of every javascript implementation on each browser. I do know however that using setTimeout, the method passed in gets called on a separate thread. So would using a setTimeout recursively inside of a method cause its stack to grow indefinitely until it causes a Stack Overflow? Or would it create a separate callstack and destroy the current frame once it goes out of focus? Here is the code that I'm wondering about.
function pollServer()
{
$.getJSON("poll.php", {}, function(data){
window.setTimeout(pollServer, 1000);
});
}
window.setTimeout(pollServer, 0);
I want to poll the server every second or so, but do not want to waste CPU cycles with a 'blocking loop' - also I do not want to set a timelimit on how long a user can access a page either before their browser dies.
EDIT
Using firebug, I set a few breakpoints and by viewing the "Script -> Stack" panel saw that the call stack is literally just "pollServer" and it doesn't grow per call. This is good - however, do any other implementations of JS act differently?
I am not sure if it would create a stack overflow, but I suggest you use setInterval if the period is constant.
This is how prototype implements its PeriodicalExecuter.
// Taken from Prototype (www.prototypejs.org)
var PeriodicalExecuter = Class.create({
initialize: function(callback, frequency) {
this.callback = callback;
this.frequency = frequency;
this.currentlyExecuting = false;
this.registerCallback();
},
registerCallback: function() {
this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
},
execute: function() {
this.callback(this);
},
stop: function() {
if (!this.timer) return;
clearInterval(this.timer);
this.timer = null;
},
onTimerEvent: function() {
if (!this.currentlyExecuting) {
try {
this.currentlyExecuting = true;
this.execute();
} finally {
this.currentlyExecuting = false;
}
}
}
});
setTimeout executes sometime later in the future in the event pump loop. Functions passed to setTimeout are not continuations.
If you stop and think about it, what useful purpose or evidencec is there that the call stack is shared by the timeout function.
If they were shared what stack would be shared from the setter to the timeout function ?
Given the setter can do a few returns and pop some frames - what would be passed ?
Does the timeout function block the original thread ?
Does the statement after the setTimeout function execute after the timeout executes ?
Once you answer those questions it clearly becomes evident the answerr is NO.
setTimeout does not grow the callstack, because it returns immediately. As for whether your code will run indefinitely in any browser, I'm not sure, but it seems likely.
take a look at the jQuery "SmartUpdater" plugin.
http://plugins.jquery.com/project/smartupdater
Following features are available:
stop() - to stop updating.
restart() - to start updating after pause with resetting time interval to minTimeout.
continue() - to start updating after pause without resetting time interval.
status attribute - shows current status ( running | stopping | undefined )
updates only if new data is different from the old one.
multiplies time interval each time when data is not changed.
handle ajax failures by stopping to request data after "maxFailedRequests".