I have a ~300 line javascript file that appears to be leaking memory, which periodically causes brief browser freezing due to what I believe is garbage collection.
This is my first attempt at writing JS, so my assumptions are pretty much guesses based on what I've been researching and using Chrome's Timeline and other tools.
Here's a very simplified version that reproduces the problem. I am positive I have other code in the script that's broken, but I think if I can get some pointers of what I may be doing wrong here, I'll be able to apply any changes to the rest of the code.
Can someone please help me understand what I could possibly do different to prevent the problem? If it's relevant, the backend I'm using to pull data from is Perl's Dancer2.
$(document).ready(function(){
var temp_limit = -1;
var humidity_limit = -1;
event_interval();
function event_interval(){
$.get('/get_config/event_display_timer', function(interval){
interval = interval * 1000;
setInterval(display_env, interval);
});
}
function display_env(){
$.get('/fetch_env', function(data){
var json = $.parseJSON(data);
});
}
});
Related
There are lots of information regarding how to check javascript memory running inside certain page, but I need to check the memory usage of my javascript inputted via console.
I am developing some scripts to work as robot like game assistant, so I put my script under the thread(frame) and triggered by setInterval calls every second. Sample code like following:
var msgList = [];
var msgGenerator = function () {
return "Welcome";
}
var msgInterval = setInterval(function () {
var msg = msgGenerator();
if (!msgList.includes(msg))
msgList.push(msg);
}, 1 * 1000);
Actually the message is very small and in total it will be less than 50. But the memory of such devtool(viewed from task manager of chrome) is increasing.
Even after I execute the following call:
clearInterval(msgInterval);
The memeory of devtool is still increasing.
Can anyone guide me how to get memory snapshot of devtool(javascript running on console)? Or is there any other way to analyze the memory leakage of homemade javascript running on console?
I have tried many ways to get the detail memory usage of devtool but failed. It seems there is no such tool/facility. Anyway, if someone found such tool later, please post in this thread.
However, I find the way to avoid memory usage increase of my script running on console: change the setInterval to setTimeout, and achieve the same effect.
The updated codes as following will not have any memory usage increasing any more.
var msgList = [];
var msgGenerator = function () {
return "Welcome";
}
var msgInterval = 0;
var msgLoop = function () {
var msg = msgGenerator();
if (!msgList.includes(msg))
msgList.push(msg);
msgInterval = setTimeout(msgLoop, 1 * 1000);
};
Hope above message can help someone is struggling to solve the "memory leakage" of console scripts or snippets.
I'm using the Howler js library to set a player in an app running through Electron. First everything seemed to work well, but after a few weeks using the app, a bug occurs repeatedly, but not constantly : the pause() function doesn't work. Here's some piece of code :
Initialization :
var is_paused = true;
var currentTrack = "track1";
var tracks = {"track1" : new Howl({urls: ['path/to/track1.mp3']}),
"track2" : new Howl({urls: ['path/to/track2.mp3']}),
"track3" : new Howl({urls: ['path/to/track3.mp3']})
};
Then I have a few buttons for play/resume, pause, stop, and play a specific track :
$('#playS').click(function(){
if (is_paused){
tracks[currentTrack].play();
is_paused = false;
}
});
$('#pauseS').click(function(){
tracks[currentTrack].pause();
is_paused = true;
});
$('.trackBtn').click(function(){
tracks[currentTrack].stop();
currentTrack = $(this).attr('id');
tracks[currentTrack].play();
is_paused = false;
});
The problem is that sometimes (generally after 40-45 min of a track playing), the pause() function just do nothing, which is really annoying cause I need to pause the track and play another 30 sec file and then resume the current track. I checked the console while the bug occurs, it says absolutely nothing. I have no idea where the bug comes from, there's not a lot of information about how works the library. I really need some help here, thank's in advance.
EDIT : one more thing, when pause() doesn't work, if I click play() the track plays from the begining, and I have control on this second instance of the track. It's like the first instance has reached end, but still playing.
Without knowing what version of Howler you're using, or what other code might be messing things up, there is one thing I think might help: You don't need to track the paused state. The "playing" method takes care of that. I've made it work using something like this:
// If it's paused, it's not playing, so...
paused = !myHowlInstance.playing();
Another thing I noticed is that you have currentTrack = $(this).attr('id'); in your (I think it's a) stop button. Unfortunately I don't know JQuery well enough to know if there's anything wrong with that (I'm more of a Dojo fan myself). But it looks like currentTrack may be set to some value not in your list (which would break tracks[currentTrack]). You might want to go into the console and type tracks["track1"], currentTrack etc. to see their values. You can even do tracks[currentTrack].play(); and see what happens. I wasn't sure if you knew you could do that (it was a huge help to me when I found out about it).
And as far as the "un-pausing" starting from the beginning, I'm currently struggling with it myself; at this time there's no clear way to do this (no resume(), pause(false) etc.), and I've seen a few more questions on the subject on Google and SO, so you're not alone. I've experimented with the seek method, but with no luck. I'll post a comment if/when I reach a breakthrough on that. :)
EDIT: I figured out the play-from-beginning thing. It does involve "seek", and also the whole "instance ID" concept (which I never really understood the importance of from the documentation).
Here's an example from a project I'm working on (also a game); it doesn't involve JQuery (sorry), but it should give you the gist of how to fix the problem.
var myBgMusic = new Howl(...);
var myMusicID = myBgMusic.play(); // The docs say play() returns an ID, and I'll be passing that to "seek" later.
var paused = false;
var saveSeek;
function TogglePause() {
if (paused) {
myBgMusic.play(myMusicID);
myBgMusic.seek(saveSeek, myMusicID);
} else {
myBgMusic.pause();
saveSeek = myBgMusic.seek(myMusicID);
}
};
I have a long-polling application written in JS fetching XML files to update a web page. It fetches every 5 seconds and is about 8KB of data. I have had this web page open for about 1 week straight (although computer goes to sleep in evening).
When first opening Chrome it starts at about 33K of my PC's memory. After I left it open for a week, constantly updating while the PC was awake, it was consuming 384K for just one tab. This is a common method that my application will be run (leaving the web page open for very long periods of time).
I feel like I am hindering Chrome's GC or am not doing some specific memory management (or maybe even a memory leak). I don't really know how a memory leak would be achievable in JS.
My app paradigm is very typical, following this endless sequence:
function getXml(file){
return $.get(file);
}
function parseXml(Xml){
return {
someTag : $(Xml).find('someTag').attr('val'),
someOtherTag: $(Xml).find('someOtherTag').attr('val')
}
}
function polling(modules){
var defer = $.Deferred();
function module1(){
var xmlData = getXml('myFile.xml').done(function(xmlData){
var data = parseXml(xmlData);
modules.module1.update(data);
}).fail(function(){
alert('error getting XML');
}).always(function(){
module2();
});
});
function module2(){
var xmlData = getXml('myFile.xml').done(function(xmlData){
var data = parseXml(xmlData);
modules.module2.update(data);
}).fail(function(){
alert('error getting XML');
}).always(function(){
defer.resolve(modules);
});
});
return defer.promise(modules);
}
$(document).on('ready', function(){
var myModules = {
module1 : new Module(),
module2 : new ModuleOtherModule()
}
// Begin polling
var update = null;
polling(myModules).done(function(modules){
update = setInterval(polling.bind(this, modules), 5000);
});
That's the jist of it... Is there some manual memory management I should be doing for apps built like this? Do I need to better management my variables or memory? Or is this just a typical symptom of having a web browser (crome/ff) open for 1-2 weeks?
Thanks
Your code seems ok but You don't posted what happens on method "udpate" inside "modules". Why I said that? Because could be that method who is leaking your app.
I recomender you two things:
Deep into update method and look how are you updating the DOM (be careful if there are a lot of nodes). Check if this content that you are updating could have associated events because if you assign a event listener to a node and then you remove the dom node, your listener still kepts in memory (until javascript garbage collector trash it)
Read this article. It's the best way to find your memory leak: https://developer.chrome.com/devtools/docs/javascript-memory-profiling
I'm using local storage because after the click, the page reloads, so I can keep track of the last item clicked.
As you can see, I've tried to clear localStorage in order to shrink the memory in use, but it's already at 1.000.000K in less then 10 minutes of usage.
Is this script redeclaring this variables at different location everytime my page reloads?
What is happening that is making it use so mant memory?
This is my entire code.
It's an extension I'm creating for chrome, it selects an option and clicks the button, the button submits a form, the page reload, and it does eveything again and again.
var last = localStorage.getItem('last');
var current = getNext(last);
var prox = getNext(current);
localStorage.clear();
$('select[name="myselect"] option').each(function(){
if($(this).val().indexOf(current)>-1){
$(this).prop('selected', true);
$('.abc').first().click();
localStorage.setItem('last',current);
}
});
function getNext(current){
var arrIds = ['227','228','229','230','231','232'];
return arrIds[arrIds.indexOf(current)+1] || '227';
}
Updated code, without var declarations, that has decreased memory consumption drastically, but with time, the memory raises (in ten minutes went from 160.000K to 240.000K):
$('select[name="myselect"] option').each(function(){
if($(this).val().indexOf(getNext(localStorage.getItem('last')))>-1){
$(this).prop('selected', true);
$('.abc').first().click();
localStorage.setItem('last',getNext(localStorage.getItem('last')));
}
});
function getNext(current){
var arrIds = ['227','228','229','230','231','232'];
return arrIds[arrIds.indexOf(current)+1] || '227';
}
As per the discussion in the comments below the question, the issue appears to come from jQuery itself. The exact reason isn't known at this point, but it appears jQuery has retained data that is not being released when the form is submitted.
This probably has to do in part with some aspect of it being a Chrome extension, since typically on a refresh, the browser would release all memory including globals.
jQuery creates great potential for memory leaks by holding data and closures in a global reference called jQuery.cache. If this is not cleaned up properly, leaks abound.
Since you're creating a Chrome extension, there shouldn't be much to compel you to use jQuery, since you'll not need to worry about browser incompatibility from browsers like IE6 and 7. By using the DOM API directly without such dependencies, the overall code will be smaller and much faster.
I had a page which executes heavy javascript code after loading. To prevent the page from freezing upon loading, I spaced the execution into batches with some "no-execution" time in between (using Timeouts), and things worked well.
Lately, I've had to add additional heavy javascript code which can execute upon client actions, yet these actions can occur even before the original heavy script is done executing. This time, spacing the action won't help, since at the "downtime" of one script the other can run and vice versa, which will cause the browser to freeze.
The problem is actually more complicated as there are multiple such actions, each executing a different heavy script, and each script sort of has a different "priority" as to how fast i'd like it to finish, compared to the other ones.
My question is, what is the common practice in such situations? I tried thinking of a way to solve it, but all I could think of was quite a complex solution which would pretty much be like writing an operating system in javascript - i.e., writing a "manager" code which executes every X time (using an "interrupt"), and chooses which "context to switch to" ( = which job should run right now), etc...
This however sounds pretty complicated to me, and I was hoping there might be other solutions out there. My problem sounds like one which I'd assume many people have stumbled upon before, so even if the only solution is what I suggested, I'd assume someone already wrote it, or there is some library support for this.
Any help would be greatly appreciated. Thank you.
== EDIT ==
by "heavy code", I mean for example the DOM manipulation of a great number of elements.
You will need to think of defining your UI/Problem domain as a set of Asynchronous tasks. Here's some more insight http://alexmaccaw.com/posts/async_ui until I formulate a better answer for you.
If you don't want to block your script you can use web workers. See MDN: Using web workers for a good introduction. Note that web workers are still relative new and not supported by most browser.
However, if you want to support all browser and add some kind of priority for your "heavy scripts", you should define something yourself, e.g:
function WorkerQueue(this_argument){
this.queue = [];
this.this_argument = this_argument;
this.priority = 1;
}
WorkerQueue.prototype.enqueue = function(callback){
this.queue.push(callback);
}
WorkerQueue.prototype.dequeue = function(){
return this.queue.splice(0,1)[0];
}
function WorkerPool(){
this.pool = [];
this.status = "running";
this.timeout = null;
}
WorkerPool.prototype.addWorker = function(this_argument){
this.pool.push(new WorkerQueue(this_argument));
return this.pool[this.pool.length - 1];
}
WorkerPool.prototype.nextTask = function(){
var max_priority = 0;
var max_priority_task = this.pool.length;
for(var i = 0; i < this.pool.length; ++i){
if(this.pool[i].priority > max_priority && this.pool[i].queue.length !== 0){
max_priority = this.pool[i].priority;
max_priority_task = i;
}
}
// pool is empty or all tasks have an invalid priority
if(max_priority_task === this.pool.length)
return;
if(this.pool[max_priority_task].this_argument)
this.pool[max_priority_task].dequeue().apply(this.pool[max_priority_task].this_argument);
else
this.pool[max_priority_task].dequeue().apply();
if(this.status !== "running")
return;
this.timeout = setTimeout(function(t){return function(){t.nextTask();};}(this),1000);
}
var Workers = new WorkerPool();
var worker1 = Workers.addWorker();
worker1.enqueue(function(){
console.log("Hello");
});
worker1.enqueue(function(){
console.log("World");
});
var worker2 = Workers.addWorker();
worker2.priority = 2;
worker2.this_argument = worker2;
worker2.enqueue(function(){
console.log("Worker 2 - changing priority");
this.priority = .2;
});
worker2.enqueue(function(){
console.log("Worker 2 - after change");
});
Workers.nextTask();
Demo
In this case, every "heavy script" is a worker, which is basically a queue of tasks. You create a new worker in the pool by using addWorker and add tasks to the specific workers queue by using worker.enqueue(callback).