Memory leak on adding Form to a DOM node

S

Simon_21

Programmers Area - Javascript Forum
thatts (Programmer)0 replies16 Sep 07 (16 Sep 07)
Memory leak on adding Form to a DOM node
Hi,

I dynamically add and remove rows to my table using DOM APIs. Inside
one of the cells in the table I have a FORM.
Now I see IE leaks the memory as time goes on, is there any way to
force IE to garbage collect.

HEre is the sample code for adding
function addRow() {
var tbl = document.getElementById('tblSample');
var row = tbl.insertRow(1);
row.insertCell(8).innerHTML="<FORM id=2828233665_form blah blah </
form>"
}

and removing using
function removeRow() {
var tbl = document.getElementById('tblSample');
var lastRow = tbl.rows.length;

tbl.deleteRow(lastRow - 1);
}

The add and delete are repeatdly done using setTimeOut();

Any thoughts ???

Thanks

_Pete
 
D

David Golightly

Programmers Area - Javascript Forum
thatts (Programmer)0 replies16 Sep 07 (16 Sep 07)
Memory leak on adding Form to a DOM node
Hi,

I dynamically add and remove rows to my table using DOM APIs. Inside
one of the cells in the table I have a FORM.
Now I see IE leaks the memory as time goes on, is there any way to
force IE to garbage collect.

HEre is the sample code for adding
function addRow() {
var tbl = document.getElementById('tblSample');
var row = tbl.insertRow(1);
row.insertCell(8).innerHTML="<FORM id=2828233665_form blah blah </
form>"

}

and removing using
function removeRow() {
var tbl = document.getElementById('tblSample');
var lastRow = tbl.rows.length;

tbl.deleteRow(lastRow - 1);

}

The add and delete are repeatdly done using setTimeOut();

Any thoughts ???

Thanks

_Pete

Could we then see the portion of your code containing the setTimeout
call? Most likely, you're dealing with a closure leak here, but I
can't be sure without seeing more.

-David
 
R

RobG

Programmers Area - Javascript Forum
thatts (Programmer)0 replies16 Sep 07 (16 Sep 07)
Memory leak on adding Form to a DOM node
Hi,

I dynamically add and remove rows to my table using DOM APIs. Inside
one of the cells in the table I have a FORM.
Now I see IE leaks the memory as time goes on, is there any way to
force IE to garbage collect.

HEre is the sample code for adding
function addRow() {
var tbl = document.getElementById('tblSample');
var row = tbl.insertRow(1);
row.insertCell(8).innerHTML="<FORM id=2828233665_form blah blah </
form>"

}

I would not expect that to cause a memory leak, some guesses:

- set tbl and row to null before exiting the function.
- don't use that shortcut method to set the cell's innerHTML

Try something like:

var table = ...;
var row = tbl.insertRow(1);
/* presumably there is code here to add cells 0 to 7 */
var cell = row.insertCell(8);
cell.innerHTML = "...";

table = null;
row = null;
cell = null;

HTH :)
 
S

Simon_21

Thanks for the replies, attached is the code, I tried Rob's
suggestion. It doesn't seem to fix the problem.


<html>

<head>
<script src='/Script/query.js' LANGUAGE='JavaScript1.2' TYPE='text/
javascript'></script>
<script src='/Script/shared.jsp' LANGUAGE='JavaScript1.2' TYPE='text/
javascript'></script>
<script src='/Script/menu.js' LANGUAGE='JavaScript1.2' TYPE='text/
javascript'></script>
<script src='/Script/tree.jsp' LANGUAGE='JavaScript1.2' TYPE='text/
javascript'></script>
<script src='/Script/SelectWindowScript.jsp' LANGUAGE='JavaScript1.2'
TYPE='text/javascript'></script>
<script src='/Script/scrolling.js' LANGUAGE='JavaScript1.2' TYPE='text/
javascript'></script>



<script type="text/javascript">
var rownum = 0;
var pause = false;
var timeout = 10;
var rowsize = 20;
function addRowToTable()
{
var tbl = document.getElementById('tblSample');
var lastRow = tbl.rows.length;

var row = tbl.insertRow(lastRow);
var cellLeft = row.insertCell(0);
cellLeft.innerHTML= "2828233665" + rownum++;
cellLeft = null;
cellLeft = row.insertCell(1);
cellLeft.innerHTML="<FORM id="+rownum++ +"
onsubmit='PopupWithFormSubmitAndParams(FindElementById(\"2828233665_form
\"),\"/Shared/Popups/id.jsp\" , \"\");return true;' action=/Shared/
Popups/id.jsp method=post target=newpopup><INPUT type=hidden
value=2828233665 name=Id> <INPUT type=hidden
value=default.domain.invalid.default.domain.invalid name=Report>
<INPUT type=hidden value=\"Aug 29, 2007 12:31:24 PM PDT\" name=Time>
<INPUT type=hidden value=\"<167>% request 12.23.62.255/137
\"
name=Message> <A onclick=\"javascript:popupWithParams('/Shared/Popups/
ID.jsp','&amp;Id=140&amp;Time=1188415884'); return false;\" href=
\"https://12.25.95.124/ingSubmit.jsp?ResubmitAndClearPaging=true#
\">default.domain.invalid.default.domain.invalid</A>&nbsp;<A
onclick='if(FindElementById(\"2828233665_form\").onsubmit())
{FindElementById(\"2828233665_form\").submit()};return false;' href=
\"https://12.25.95.124/ingSubmit.jsp?ResubmitAndClearPaging=true#\"
alt=\"[id]\"></A> &nbsp;<A onclick=\"javascript:popupWithParams( '/
Shared/Popups/ery.jsp', '&amp;t_id=2828233665' ); return false;\" href=
\"https://12.25.95.124/mit.jsp?ResubmitAndClearPaging=true#\"> </A></
FORM>";

cellLeft = null;
row = null;
tbl = null;
lastRow = null;
}

function removeRowFromTable()
{
var tbl = document.getElementById('tblSample');
var lastRow = tbl.rows.length;

if (lastRow > 1) {
tbl.deleteRow(1);
}
tbl = null;
}



function fillTable() {
var tbl = document.getElementById('tblSample');
var lastRow = tbl.rows.length;

for (i = 0; i < lastRow; i++) {
removeRowFromTable();
}

for (i = 0; i < rowsize; i++) {
addRowToTable();
}

setTimeout("addRemove();", timeout);
}

function addRemove() {
if (pause)
return;
removeRowFromTable();
// CollectGarbage();
addRowToTable();
setTimeout("addRemove();", timeout);

}



</script>
</head>

<body onload="fillTable();">

<p>
<input type="button" value="Pause" onclick="if (pause) {this.value =
'Pause'; pause = false; setTimeout('addRemove();', timeout);} else
{ this.value = 'Resume'; pause = true;}" />
</p>

<br>
Rows: <input type="text" name="rows" value="20"/> rows
<input type="button" value="Set Rows" onclick="rowsize=rows.value;
fillTable();" />


<div id="displaytable" style="position:relative;width:970px;height:
0px;overflow:hidden">
<table border="1" id="tblSample" width="100%";>


<tr>
<th width="10%">ID1</th>

<th width="90%">ID</th>
</tr>
</table>
</div>
</body>
</html>
 
R

RobG

Thanks for the replies, attached is the code, I tried Rob's
suggestion. It doesn't seem to fix the problem.

<html>
<head>
<script src='/Script/query.js' LANGUAGE='JavaScript1.2' TYPE='text/
javascript'></script>

When posting code, manually wrap it at about 70 characters to avoid
auto-wrapping causing errors. Only post a minimal working example
that shows the problem - if those six script files aren't relevant to
the issue, don't include them.

The language attribute is deprecated, using that particular value is
also problematic in some older browsers. Just remove it.


[...]
<script type="text/javascript">
var rownum = 0;
var pause = false;
var timeout = 10;
var rowsize = 20;

You should avoid large numbers of global variables, there are
strategies to deal with this.

function addRowToTable()
{
var tbl = document.getElementById('tblSample');
var lastRow = tbl.rows.length;

var row = tbl.insertRow(lastRow);
var cellLeft = row.insertCell(0);
cellLeft.innerHTML= "2828233665" + rownum++;
cellLeft = null;

There is no need to set cellLeft to null here.

cellLeft = row.insertCell(1);
cellLeft.innerHTML="<FORM id="+rownum++ +"

An ID value is not allowed to start with a number - though it's
unlikely to cause a problem, it isn't valid.


[... huge slab of innerHTML removed ...]

Don't use such a very large slab of innerHTML as a single string.
Break it up into a number of lines by:

1. Ensuring that it works as HTML
2. Breaking it into manageable pieces and
ensuring that it works with document.write
3. Putting it into the function and ensuring it works

cellLeft = null;
row = null;
tbl = null;
lastRow = null;

Nice try, but you say it doesn't work... :-(
That strategy is handy to avoid memory leaks with circular refernces,
but you don't seem to have any. Of course I can't tell what is
happening in those 6 script files.

}

function removeRowFromTable()
{
var tbl = document.getElementById('tblSample');
var lastRow = tbl.rows.length;

if (lastRow > 1) {
tbl.deleteRow(1);
}
tbl = null;

Again, not necessary.
}

function fillTable() {
var tbl = document.getElementById('tblSample');
var lastRow = tbl.rows.length;

for (i = 0; i < lastRow; i++) {
removeRowFromTable();
}

for (i = 0; i < rowsize; i++) {
addRowToTable();
}

setTimeout("addRemove();", timeout);

}

function addRemove() {
if (pause)
return;
removeRowFromTable();
// CollectGarbage();
addRowToTable();
setTimeout("addRemove();", timeout);

}

</script>
</head>

<body onload="fillTable();">

<p>
<input type="button" value="Pause" onclick="if (pause) {this.value =
'Pause'; pause = false; setTimeout('addRemove();', timeout);} else
{ this.value = 'Resume'; pause = true;}" />

Don't use XHTML markup in an HTML document.
</p>

Delay: <input type="text" name="delay" value="40"/> ms
<input type="button" value="Set Delay" onclick="timeout=delay.value" />

Where is delay.value set? Do not depend on IE's quirk of setting
names and IDs as global variables, consider:

<br>
Rows: <input type="text" name="rows" value="20"/> rows
<input type="button" value="Set Rows" onclick="rowsize=rows.value;
fillTable();" />

Same here.

<div id="displaytable" style="position:relative;width:970px;height:
0px;overflow:hidden">

I don't know what that does in IE, but in Firefox and Safari, that
makes your table contents invisible.


Here is a trimmed-down version that "works" and does not seem to hog
memory in Firefox, Safari or IE.


<html>
<head> <title>foo</title>
<script type="text/javascript">
var rownum = 0;
var pause = false;
var timeout = 10;
var rowsize = 20;
function addRowToTable()
{
var tbl = document.getElementById('tblSample');
var row = tbl.insertRow(-1);
var cellLeft = row.insertCell(0);
cellLeft.innerHTML= "2828233665 " + rownum++;
cellLeft = row.insertCell(1);
cellLeft.innerHTML = 'foo';
}

function removeRowFromTable()
{
var tbl = document.getElementById('tblSample');
var lastRow = tbl.rows.length;
if (lastRow > 1) {
tbl.deleteRow(1);
}
tbl = null;

}

function fillTable() {
var tbl = document.getElementById('tblSample');
var lastRow = tbl.rows.length;

for (var i=0; i<lastRow; i++) {
removeRowFromTable();
}

for (i=0; i<rowsize; i++) {
addRowToTable();
}

setTimeout("addRemove();", timeout);

}

function addRemove() {
if (pause)
return;
removeRowFromTable();
addRowToTable();
setTimeout("addRemove();", timeout);
}

</script>
</head>
<body onload="fillTable();">

<p>
<input type="button" value="Pause" onclick="
if (pause) {
this.value = 'Pause';
pause = false;
setTimeout('addRemove();', timeout);
} else {
this.value = 'Resume'; pause = true;}
">
</p>

Delay: <input type="text" name="delay" value="40"> ms
<input type="button" value="Set Delay" onclick="timeout=this.value">
<br>
Rows: <input type="text" name="rows" value="20"> rows
<input type="button" value="Set Rows" onclick="rowsize=this.value;
fillTable();" >
<div id="displaytable">
<table border="1" id="tblSample" width="100%">
<tr>
<th width="10%">ID1</th>
<th width="90%">ID</th>
</tr>
</table>
</div>
</body>
</html>
 
S

Simon_21

Thanks Once again for the detailed explnation/suggestions. I am just
translating from java/c++ to javascript.

Okay I modified the code you send just to show the memory leak. I have
been looking through various articles and forums to find a solution to
this problem. No luck so far :(:(

The only change from your code is to set a form in the second cell
(actually a simple form)

<html>
<head> <title>foo</title>
<script type="text/javascript">
var rownum = 0;
var pause = false;
var timeout = 10;
var rowsize = 20;
function addRowToTable()
{
var tbl = document.getElementById('tblSample');
var row = tbl.insertRow(-1);
var cellLeft = row.insertCell(0);
cellLeft.innerHTML= "2828233665 " + rownum++;
cellLeft = row.insertCell(1);
cellLeft.innerHTML ="<FORM id="+ "form_"+rownum++ +
"<A>default.domain.invalid.default.domain.invalid</A></FORM>";
cellLeft = null;




}


function removeRowFromTable()
{
var tbl = document.getElementById('tblSample');
var lastRow = tbl.rows.length;
if (lastRow > 1) {
tbl.deleteRow(1);
}
tbl = null;


}


function fillTable() {
var tbl = document.getElementById('tblSample');
var lastRow = tbl.rows.length;

for (var i=0; i<lastRow; i++) {
removeRowFromTable();
}


for (i=0; i<rowsize; i++) {
addRowToTable();
}


setTimeout("addRemove();", timeout);



}


function addRemove() {
if (pause)
return;
removeRowFromTable();
addRowToTable();
setTimeout("addRemove();", timeout);


}


</script>
</head>
<body onload="fillTable();">

<p>
<input type="button" value="Pause" onclick="
if (pause) {
this.value = 'Pause';
pause = false;
setTimeout('addRemove();', timeout);
} else {
this.value = 'Resume'; pause = true;}
">
</p>


Delay: <input type="text" name="delay" value="40"> ms
<input type="button" value="Set Delay" onclick="timeout=this.value">
<br>
Rows: <input type="text" name="rows" value="20"> rows
<input type="button" value="Set Rows" onclick="rowsize=this.value;
fillTable();" >
<div id="displaytable">
<table border="1" id="tblSample" width="100%">
<tr>
<th width="10%">ID1</th>
<th width="90%">ID</th>
</tr>
</table>
</div>
</body>
</html>
 
D

David Golightly

Thanks Once again for the detailed explnation/suggestions. I am just
translating from java/c++ to javascript.

Okay I modified the code you send just to show the memory leak. I have
been looking through various articles and forums to find a solution to
this problem. No luck so far :(:(

The only change from your code is to set a form in the second cell
(actually a simple form)

<html>
<head> <title>foo</title>
<script type="text/javascript">
var rownum = 0;
var pause = false;
var timeout = 10;
var rowsize = 20;
function addRowToTable()
{
var tbl = document.getElementById('tblSample');
var row = tbl.insertRow(-1);
var cellLeft = row.insertCell(0);
cellLeft.innerHTML= "2828233665 " + rownum++;
cellLeft = row.insertCell(1);
cellLeft.innerHTML ="<FORM id="+ "form_"+rownum++ +
"<A>default.domain.invalid.default.domain.invalid</A></FORM>";
cellLeft = null;

}

function removeRowFromTable()
{
var tbl = document.getElementById('tblSample');
var lastRow = tbl.rows.length;
if (lastRow > 1) {
tbl.deleteRow(1);
}
tbl = null;

}

function fillTable() {
var tbl = document.getElementById('tblSample');
var lastRow = tbl.rows.length;

for (var i=0; i<lastRow; i++) {
removeRowFromTable();
}

for (i=0; i<rowsize; i++) {
addRowToTable();
}

setTimeout("addRemove();", timeout);

}

function addRemove() {
if (pause)
return;
removeRowFromTable();
addRowToTable();
setTimeout("addRemove();", timeout);

}

</script>
</head>
<body onload="fillTable();">

<p>
<input type="button" value="Pause" onclick="
if (pause) {
this.value = 'Pause';
pause = false;
setTimeout('addRemove();', timeout);
} else {
this.value = 'Resume'; pause = true;}
">
</p>

Delay: <input type="text" name="delay" value="40"> ms
<input type="button" value="Set Delay" onclick="timeout=this.value">
<br>
Rows: <input type="text" name="rows" value="20"> rows
<input type="button" value="Set Rows" onclick="rowsize=this.value;
fillTable();" >
<div id="displaytable">
<table border="1" id="tblSample" width="100%">
<tr>
<th width="10%">ID1</th>
<th width="90%">ID</th>
</tr>
</table>
</div>
</body>
</html>

A couple of things I'd change:

1. Do what it takes to reduce the number of global variables you use,
for example:

instead of:
<input type="button" value="Set Rows" onclick="rowsize=this.value;
fillTable();" >

write:
<input type="button" value="Set Rows" onclick="fillTable(this);">

and in the code for fillTable, extract the rowsize from the input
element reference value.

2. move lengthy code out of inline event handlers and into script
code:

<input type="button" value="Pause" onclick="
if (pause) {
this.value = 'Pause';
pause = false;
setTimeout('addRemove();', timeout);
} else {
this.value = 'Resume'; pause = true;}
">

should look like:

<input type="button" value="Pause" onclick="pauseClick(this);">

function pauseClick(button) {
if (button.value == 'Resume') {
button.value = 'Pause';
setTimeout(addRemove, timeout);
} else {
button.value = 'Resume';
}
}

(also see how to get rid of the global variable "pause"? Keeping state
in the DOM is probably a good idea in this case)

Also, pass in the function reference to setTimeout, not a string-
you'll save a call to "eval":

setTimeout(addRemove, timeout);

Try these things; if they don't fix your problem, I suspect the memory
leak may come from the addRowToTable function - the DOM nodes added
via innerHTML may not be getting garbage-collected - and "forcing" a
garbage collection isn't going to solve your problem - the GC may be
passing over these nodes for some reason.

-David
 
S

Simon_21

Hi David,

I have found the exact point where the memory leak happens,

it is in addRowToTable()
cellLeft.innerHTML ="<FORM id="+ "form_"+rownum++ +
"<A>default.domain.invalid.default.domain.invalid</A></FORM>";

The moment I change the form to something else the leak stops, but I
don't have a clue why it leaks.

Thanks
Rajesh
 
T

Thomas 'PointedEars' Lahn

Simon_21 said:
it is in addRowToTable()
cellLeft.innerHTML ="<FORM id="+ "form_"+rownum++ +
"<A>default.domain.invalid.default.domain.invalid</A></FORM>";

The moment I change the form to something else the leak stops, but I
don't have a clue why it leaks.

First, there is useless concatenation.

cellLeft.innerHTML = '<FORM id="form_' + (rownum++) +
'<A>default.domain.invalid.default.domain.invalid</A></FORM>';

Second, there is too much concatenation.

cellLeft.innerHTML = new Array(
"<FORM id='form_", rownum++,
"<A>default.domain.invalid.default.domain.invalid</A></FORM>"
).join("");

Third, this generates invalid markup, the <form> tag is incomplete.
Also, attribute values are not quoted (not a syntax error, but a
possible cause for the leak as the UA has to parse the markup).

cellLeft.innerHTML = new Array(
'<FORM id="form_', rownum++, '">',
"<A>default.domain.invalid.default.domain.invalid</A></FORM>"
).join("");

Fourth, the generating markup is not Valid itself: ETAGOs must be escaped
in HTML `script' element content or the element ends prematurely.

cellLeft.innerHTML = new Array(
'<form id="form_', rownum++, '">',
'<a>default.domain.invalid.default.domain.invalid<\/a><\/form>'
).join("");

Fifth, the generated markup is useless: the generated `a' element has
neither a `name', nor an `id' or a `href' attribute. What is the UA
supposed to do with that?

And don't forget to include

cellLeft = null;

after that.

But you have not posted the real code, so the above can only give you a hint
as to possible improvements of your code that might or might not solve the
problem. Also, from the sheer length of the wrapped code posted in
<[email protected]>, it would not be
unreasonable to assume that MSHTML has its problems with that long a line.

That said, the proprietary `innerHTML' property should not be used anyway.
If you build the subtree with DOM Level 2 methods, you would stand much
less a chance to run into such apparent flaws of the DOM implementation.


HTH

PointedEars
 
S

Simon_21

The code I send was just a demo code to show the memory leak problem.
Earlier I had send the entiire form content. Since it was not relevant
to the discussion I just created a dummy form to show the issue.
Having said that I have followed your instructions and corrected it.

Still I am not sure why memory leaks. You suggested not to use
innerHTML, what is the other alternative ? I could try that out.

Thanks
 
R

RobG

I have found the exact point where the memory leak happens,

it is in addRowToTable()
cellLeft.innerHTML ="<FORM id="+ "form_"+rownum++ +
"<A>default.domain.invalid.default.domain.invalid</A></FORM>";

The moment I change the form to something else the leak stops, but I
don't have a clue why it leaks.

It seems that the form element is the problem, - you don't even have
to add it to the form, simply creating a form and adding a child
element is sufficient - below is a test case.

It takes a little while to start consuming memory, and it seems to be
returned when the page is refreshed so it isn't a big problem unless
you intend replacing a few hundred forms without refreshing the page.

A work around may be that rather than replacing the form, you re-use
one that is already in the page, or force a re-load from time to time.


<script type="text/javascript">
var addRows = (function()
{
var timeout,
tableID,
c = 0,
lag = 50;
wait = false;

function replaceRow(){
var el = document.getElementById(tableID);
var row = el.insertRow(-1);
var cell = row.insertCell(-1);
var f = document.createElement('form');
var a = document.createElement('a');

a.href = '#';
a.appendChild(document.createTextNode(c++ +
' default.domain.invalid.default.domain.invalid')
);
f.appendChild(a);
cell.appendChild(f);
el.deleteRow(0);

a = null;
f = null;
row = null;
cell = null;
el = null;
}

return {
start: function(id){
tableID = id;
wait = false;
addRows.run();
},
run: function(){
if(!wait){
replaceRow();
timeout = setTimeout(addRows.run, lag);
} else {
clearTimeout(timeout);
}
},
pause: function(){
wait = true;
}
}
})();
</script>

<input type="button" onclick="addRows.start('xx')"
value="Start addRows">
<input type="button" onclick="addRows.pause()"
value="Pause addRows">

<table id="xx">
<tr><td>a table
</table>
 
T

Thomas 'PointedEars' Lahn

Simon_21 said:
Still I am not sure why memory leaks. You suggested not to use
innerHTML, what is the other alternative ? I could try that out.

Googling for "DOM Level 2" did not help?


PointedEars
 
S

Simon_21

Thanks to everyone for the feedback, especially to Rob for putting so
much effort.

However, I was not able to fix the problem. Even with form reusing
after a while there is some leak. Also form reusing was not an option
for me as I need to make major javascript code change in the legacy
code.
(we are adding rows at the rate of 20 ms, so that is 50 * 60 * 60 =
180000 rows per hour)

So I decided not to use the form at all. (The reason we had the form
in the cell was to send a bulk data back to the server that is only
available on the client. Since it was a bulk data we had to use 'POST'
and hence form.)

I changed the code to have an AJAX call to the server when the user
clicks on the link and put the data in the servlet. Since AJAX
supports POST I was able to replace the FORM tag with regular link and
that solved my memory leak.

Still I don't know why it leaks, and I thought I already spend few
days on it so gave it up. Writing it down for any one who sees such a
problem later.

Btw: This forum is great, you all have been very helpful.

_Pete
 
T

Thomas 'PointedEars' Lahn

Simon_21 said:
However, I was not able to fix the problem. Even with form reusing
after a while there is some leak.

Probably there are some unfixed closures left.
[...]
I changed the code to have an AJAX call to the server when the user
clicks on the link and put the data in the servlet. Since AJAX
supports POST I was able to replace the FORM tag with regular link and
that solved my memory leak.

That was an unwise decision. Now you rely on "AJAX" being supported.
The requirements for that are even more than those for client-side script
support.
[...]
Btw: This forum is great, you all have been very helpful.

It's a public newsgroup, which is only archived at Google. Not a Web forum.

Thanks anyway.


PointedEars
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,982
Messages
2,570,190
Members
46,736
Latest member
zacharyharris

Latest Threads

Top