Skip to content

Instantly share code, notes, and snippets.

@lsmith
Created August 1, 2009 04:50
Show Gist options
  • Save lsmith/159572 to your computer and use it in GitHub Desktop.
Save lsmith/159572 to your computer and use it in GitHub Desktop.
This gist aims to prove or disprove the assertion made here[1] and linked on
Ajaxian[2] that dynamic script requests, or more specifically JSONP requests,
leak memory in all modern browsers.
The specific setup is a 200ms interval that requests an uncached script pointed at
the simple php file. This file in response sends the following string:
update()
This is not technically a JSONP response, since the would-be callback is executed
without arguments. However, memory footprint results can be gauged on generic
script content rather than focused on the JSONP pattern.
With all versions of this setup in the gist history, there IS a memory leak.
Log:
version 1: use a simple xbrowser getScript in the setup described
version 2: move the script node clearing code out of the getScript function to test
for closure related leakage. Add the "About this gist" file to track progress.
version 3: formatting updates to "About this gist" file.
version 4: testing setting defer to true (no change)
version 5: wrapping node creation in try/finally to null node references (no change)
version 6: calling script.clearAttributes() if available (IE) before node removal. Interestingly, this did seem to flatline the mem footprint until about 500 requests, at which point memory climbed rapidly.
version 7: per Andrea Giammarchi's destroy suggestion[3][4], added while loop to remove
all childNodes before removing from the DOM (no change)
version 8: moved the clearAttributes and childNode removal after the script node
removal. Again, seemed flat until ~400 requests, then shot up.
version 9: replaced clearAttributes call with manual iteration of script props,
assigning each to null inside a try/catch because IE throws errors. Memory
consumption shot up faster than any prior configuration.
version 10: replaced property assignment to null with deleting properties. This seriously limited the leak (*validates the author's assertion*).
Browser BEFORE AFTER
IE6 (forthcoming, but it was an obvious decrease in Drip)
Saf4 +6.5M/500 +2M/500
FF3.5 +0/500 (no leak?)
other browsers forthcoming
version 11: Changed to Process Explorer[5] from Drip (via @tobie) to avoid false
positives. Switched to a new FF profile with no plugins to avoid false positive from
add-ons (I'm looking at you FireBug). Returned to onload = null approach, removed
defer = false, and did lengthier trials.
Results:
Browser RUN START END REQs NOTES
IE6 1/1 16M/10M 26M/20M ~18600 (Physical Mem/Virtual Mem)
FF3.5 1/2 66.33M 66.99M 500 memory returned to normal after a few secs
2/2 54.39M 58.91M 22400 memory did not return after 5mins
Saf4 1/1 66.16M ~104M 8400 yikes! Opening Web Inspector brought app
to a crawl and mem jumped to 384M.
Resources tab showed every request.
IE8 1/1 39M/33M 55M/48M 19939 IE8 mode (not IE7 compat mode)
version 12: Reapplied delete-all-properties patch for retest.
Results:
Browser RUN START END REQs NOTES
IE6 1/1 16M/10M 21M/14M 8046 Patch didn't work? I cleared cache and
viewed source to make sure the script
state was correct. Test cut short because
it's past bed time.
FF3.5 1/1 51.38M 53.86M 17664 The clear winner here.
Saf4 1/1 29.54 149.36M 17619 The clear loser.
IE8 1/1 18M/16M 39M/35M 17224
1. http://neil.fraser.name/news/2009/07/27/
2. http://ajaxian.com/archives/dynamic-script-generation-and-memory-leaks
3. http://webreflection.blogspot.com/2009/04/drip-under-control-via-another-ie.html
4. http://ajaxian.com/archives/dynamic-script-generation-and-memory-leaks#comment-274727
5. http://technet.microsoft.com/en-us/sysinternals/bb896653.aspx
<?php
header("Cache-control: no-cache");
header("Expires: -1");
header("Content-type: text/javascript");
echo "update()"
?>
<!doctype html>
<html>
<head>
<title>Test Page</title>
</head>
<body>
<p id="inc">0</p>
<p id="remaining">0</p>
<button id="go">Start</button>
<script type="text/javascript">
var timer = null,
update_int = null,
count = 0;
// Executed by the requested scripts
function update() { ++count; }
function clearScript() {
var hasReadyState = ('readyState' in this);
if (!hasReadyState || /loaded|complete/.test(this.readyState)) {
this.parentNode.removeChild(this);
for (var k in this) {
try {
delete this[k];
}
catch (e) {}
}
}
}
function getScript(U){
var h = document.documentElement.firstChild,
s = document.createElement('script');
try {
s[('readyState' in s) ? 'onreadystatechange' : 'onload'] = clearScript;
s.src = U;
h.insertBefore(s,h.firstChild);
}
finally {
s = h = null;
}
}
document.getElementById('go').onclick = function () {
if (timer) {
clearInterval(timer);
timer = null;
update();
clearInterval(update_int);
update_int = null;
this.innerHTML = "Start";
} else {
timer = setInterval(function () {
getScript('script_leak.php?x='+(new Date().getTime()));
},200);
update_int = setInterval(function () {
document.getElementById('inc').innerHTML = count;
document.getElementById('remaining').innerHTML = document.getElementsByTagName('script').length;
},1000);
this.innerHTML = "Stop";
}
};
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment