Memory leak in fnClearTable()?

Memory leak in fnClearTable()?

mhurnemhurne Posts: 4Questions: 0Answers: 0
edited August 2010 in General
I believe I may be seeing a memory leak related to fnClearTable(). I have a simple test page written that sets up sample data and uses fnAddData() to add it to the table. It then uses fnClearTable() to clear the data from the table, sets up a new set of sample data, and uses fnAddData() to put the new data into the table. This process is repeated every 10 seconds. I had the program running for over 24 hours, and periodically took screenshots showing the program, Google Chrome's Task Manager (which shows the program's "private memory" usage), and top (sorted by VIRT memory usage). When I took the first screenshot after 3,116 data reloads, Chrome showed the page's private memory usage as 617,840K. top showed the process's memory usage as 1363m (note that in the screenshots, the process' PID is 2548). Just over 24 hours later, Chrome showed the page's private memory usage as 963,552K while top showed the process' memory usage as 1737m. While this particular test doesn't show extreme growth of memory usage over time, our production application does; when I left it running overnight doing a similar reload process, Chrome had killed the tab sometime during the night.

Has anyone else seen this type of behavior? Is it a memory leak in DataTables? If so, is there any way I can help track it down and get it fixed?

You can download the test program here:
http://dl.dropbox.com/u/5415472/DataTables%20Memory%20Leak%20Test.zip

You can download the set of screenshots taken over the 24-hour test period here:
http://dl.dropbox.com/u/5415472/DataTables%20Memory%20Leak%20Screenshots.zip

Here's the code inline:

[code]
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">



DataTables Memory Leak Example







var goTable;
var giColCount = 10;
var giRowCount = 10000;
var giLoadCount = 0;

function fnClearTable() {
goTable.fnClearTable();
}

function fnLoadTable() {
var aaData = [];
for (var iRowIndex = 0; iRowIndex < giRowCount; iRowIndex++) {
aaData[iRowIndex] = [];
for (var iColIndex = 0; iColIndex < giColCount; iColIndex++) {
aaData[iRowIndex][iColIndex] = sprintf("Load #%'05d
Row #%'05d
Column #%'05d", giLoadCount, iRowIndex, iColIndex);
}
}
goTable.fnAddData(aaData);
giLoadCount++;
}

function fnClearAndLoadTable() {
fnClearTable();
fnLoadTable();
}

$(function() {
for (var iColIndex = 0; iColIndex < giColCount; iColIndex++) {
$('#testTable thead tr').append('Column ' + iColIndex + '');
}

goTable = $('#testTable').dataTable({
"bJQueryUI": true,
"sPaginationType": "full_numbers"
});

$('.loadButton').click(function() {
fnLoadTable();
});

$('.clearButton').click(function() {
fnClearTable();
});

setInterval(fnClearAndLoadTable, 10 * 1000);
});




DataTables Memory Leak Example



Load Data
Clear Data
















Load Data
Clear Data





[/code]

Replies

  • kkudikkudi Posts: 77Questions: 0Answers: 0
    I'd be interested in this one. Unfortunately can't really offer much help. Allan is probably the best one to answer
  • mhurnemhurne Posts: 4Questions: 0Answers: 0
    Thanks for the interest, kkudi. I actually spent a good amount of time inside the DataTables source code trying to track this down, but I guess I haven't yet developed the JavaScript expertise needed since I was unsuccessful!
  • allanallan Posts: 63,523Questions: 1Answers: 10,473 Site admin
    Interesting one! I don't have an immediate answer for this one I'm afraid, I'll need to spend a bit of time doing some investigation. At a guess I would say the most obvious candidate is that the method I use for emptying the arrays (which is a bit rubbish actually looking at it...) perhaps means that the data isn't being correctly cleaned up by the browser's garbage collector. I don't think there any references left hanging around, so it is possible that the garbage collector just isn't being very aggressive - or possibly a bug in the browser's Javascript engine...

    Regards,
    Allan
  • mhurnemhurne Posts: 4Questions: 0Answers: 0
    Thanks Allan. I'm curious if anyone else has observed this issue? I plan on running the test program in a different browser (say, Firefox and/or Opera as the only open tab) to see if the issue shows up there as well. I'll let you know what I find after I do so.
  • mhurnemhurne Posts: 4Questions: 0Answers: 0
    edited August 2010
    I ran the test in Firefox for just under 24 hours, and observed a memory usage increase of roughly 419-425 MB. I did modify the program slightly; each set of data was 1,000 rows rather than 10,000. The data was reloaded every 5 seconds rather than every 10 seconds. These changes were necessary since Firefox was having a hard time handling the 10,000 row reload every 10 seconds. I also added some Firebug console.time() and console.timeEnd() calls to see how long it was taking to clear the table and generate and load new data into the table.

    You can download the updated test program here:
    http://dl.dropbox.com/u/5415472/DataTables%20Memory%20Leak%20Test%202010-08-18.zip

    You can download the set of screenshots taken over the Firefox test period here:
    http://dl.dropbox.com/u/5415472/DataTables%20Memory%20Leak%20Test%20Screenshots%20-%20Firefox.zip
  • allanallan Posts: 63,523Questions: 1Answers: 10,473 Site admin
    I've been doing a bit of testing the IE specific leak detection tools ( http://home.orange.nl/jsrosman/ ) and haven't been able to find any leaks in DataTables. IE appears to be buzzing along happily at the moment. I'll spend a bit more time on this in future, as it certainly looks like you've found something mhurne, but I'm not sure how to track it down. For now, I've altered how I empty the table arrays to use a splice rather than just setting the length to 0, which is a bit nasty. This will be in 1.7.1.

    Regards,
    Allan
  • kabakovkabakov Posts: 4Questions: 0Answers: 0
    I have same problems jquery.dataTables.js

    Every 15 seconds i loading 2Mb of json, clearing table and adding 3000 rows to it.
    Every query increases memory usage by 10Mb
    Google chrome tab crashing in 15 minutes
  • allanallan Posts: 63,523Questions: 1Answers: 10,473 Site admin
    I don't believe then fnClearData is actually leaking (although make sure you clear any event handlers you've added to the rows / cells in the table!). I ask the Chrome dev list about this: http://groups.google.com/a/chromium.org/group/chromium-discuss/browse_thread/thread/83d9ae67a2772cbd

    One of the replies:

    -------
    Your example doesn't contain any leaks -- if you manually collect garbage after the loop has stopped, memory usage drops back to initial value.
    I think that GC is just never too aggressive unless the system starts running low on memory.
    -------

    So I'm not sure what to do about it. There is no leak in DataTables there that I can see, it's just the GC doesn't keep up. Another member of the forum (GregP) is interested in this topic as well - you might be interested in his recent post: http://datatables.net/forums/comments.php?DiscussionID=5118

    Allan
  • GregPGregP Posts: 500Questions: 10Answers: 0
    edited May 2011
    Indeed, very interested in this topic! After much trouble-shooting (including manually deleting objects and nulling variables), I couldn't pinpoint it, leaving me to conclude that it's the browser. As Allan says, GC might not be aggressive enough. It's worth noting that I am NOT in fact using the same functions, though the same methods are doubtless called internally. I am using fnDraw at each polling interval instead.

    Is there a way to manually force garbage collection? I was not aware of that.

    My compromise ended up being something that's probably not suited to your goals. In my application, it is likely for the end-user to simply go away from their desk or otherwise stop interacting with the page. I hacked together a pausing API that allows you to either manually pause, programmatically pause, or have idle/away events trigger the pause. When paused, memory consumption does not grow, and if I'm not mistaken the GC will eventually circle around and free up the memory.

    It's smack-dab in the middle of "workaround" territory. Although the pause will be useful to us for other reasons, I would still like to find a solution to any memory leaks, full-stop.
  • kabakovkabakov Posts: 4Questions: 0Answers: 0
    Hi! Problem solved for me!

    """although make sure you clear any event handlers you've added to the rows / cells in the table!"""

    I binded click event to every row adn then deleted it and memory was not freed
    Now I'am unbind event handlers from rows before fnClearTable call and memory consumption is stable

    Thanks
  • GregPGregP Posts: 500Questions: 10Answers: 0
    Ohsnap! I learned the beauty of jQuery's .delegate() recently but I don't think I'm using it across my whole application yet. That might be it for me, too!

    If you haven't started using .delegate() instead of .click() or other binding methods yet, I suggest you look into it. Delegate to a parent that is not part of the DataTables object and you don't have to manually destroy anything ever.
  • kabakovkabakov Posts: 4Questions: 0Answers: 0
    GregP,
    I've tried to use .delegate() function but memory started to leak again

    What I do is
    """$("#table_id").delegate("td", "click", clickhandler);"""
    Right after datatable initializaton
    I don't clearing event handlers, and i've got memory leak again
    I think that happens because browser keeping reference to row from event handler and can't free it's memory...

    So even when using delegate I still have to clear event handlers before table fnClearTable
  • GregPGregP Posts: 500Questions: 10Answers: 0
    edited May 2011
    You shouldn't have to; you just need to delegate higher in the DOM. The #table_id refers to the table itself; if you have a wrapper div even higher than that (don't make a new one, just use whatever's available) you can still pull it off.

    $('#wrapper').delegate('table td', 'click', clickHandler);

    or if you couldn't be fussed manually deciding which wrapper to use, this should work in most scenarios. You just go "up" to whatever the parent is before delegating to an element within.

    $('#table_id').parent().delegate('table td', 'click', clickHandler);

    The wrapper/parent becomes the listener, not the table. To be honest, I haven't tried implementing it myself yet (trying to work on column resizing first) but if you get around to it, I'd love to hear your results. If I get around to it first, I'll let you know how it goes.
  • GregPGregP Posts: 500Questions: 10Answers: 0
    edited June 2011
    Yeah, I switched to .delegate, and bound to elements higher in the DOM but must admit to no love.

    I don't really know which events I need to unbind, or even how to unbind them. Would be interested in specific examples of how you got memory consumption stable, if you have them!
  • GregPGregP Posts: 500Questions: 10Answers: 0
    Did a bog-standard deployment. No bindings, no DOM manipulation... and I STILL have memory issues.

    I feel dumb for chasing a technical solution for so long now, but I think... I just have my polling interval too fast with no sanity check for completion before starting the next one. I think it's causing some timing to go wonky.

    Even dumber: I'm pretty sure Allan suggested this could be my issue ages ago and I forgot about it. ;-) I'll have to implement a better polling mechanism. Instead of an interval between fnDraws, it will be an interval from when Draw completes (no matter how long that takes... could be a second or two depending on connection) to when it next polls for data.

    Writing something that takes into account possible errors in the Ajax request... could be more involved than it sounds on the surface... ;-)

    Imagine if you could just set "dataSource = "? ;-)
  • allanallan Posts: 63,523Questions: 1Answers: 10,473 Site admin
    setInterval should be used when you know that you want to do things in intervals (say every 2 seconds), since it will always keep to that, while setTimeout will add a delay due to the processing of the function (depending of course when you set up the next setTimeout call). However as you mention in either case it's important to not have XHRs overlapping for the same data set - weird things can happen then :-)

    > Imagine if you could just set "dataSource = "? ;-)

    Hmmm - sounds like a grand idea for a plug-in that... :-)

    Allan
This discussion has been closed.