I was surprised to find out last week that Ajax calls made with jQuery 1.4.2 leak memory in Internet Explorer 8.
The web application I am working on includes a page on which a user will spend much of their time. As the user navigates through various subsections of the page (we are using sammy.js for client side “pages”) repeated Ajax requests are made to the server for JSON data. Our exploratory tester discovered that the page was slowing down dramatically in IE8 the longer he navigated through the subsections. Further investigation revealed that the browser’s memory usage was increasing dramatically over time.
After a fair amount of trial and error I was finally able to narrow the leak down to the Ajax calls. I made a test html file that did nothing but make repeated Ajax requests and watched the memory increase. My test page used jQuery 1.4.2 because that is what the application was using.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<html> <head> <script type="text/javascript" src="/javascripts/jquery-1.4.2.js"></script> <script type="text/javascript"> var requestData = function() { $.ajax({ url: 'http://localhost/data.json', type: 'GET', contentType: 'application/json', complete: function() { setTimeout(requestData, 100); } }); } $(requestData) </script> </head> <body> Memory Leak Testing... </body> </html> |
Knowing it had something to do with Ajax I was able to find a stackoverflow question that described my problem exactly. One of the answers referenced an open ticket on the jQuery project site that included a patch for jQuery 1.4.2. The ticket has been open for 7 months so far and has yet to be corrected in the jQuery code base. A commenter on the submitted ticket indicated this was also a problem in IE7, but I have not confirmed that myself.
I applied the patch to my local copy of jQuery 1.4.2, ran my test page, and happily watched as the memory no longer increased over time.
The original jQuery code (around line 5223):
1 2 3 |
if ( s.async ) {
xhr = null;
}
|
The modified jQuery code:
1 2 3 |
if ( s.async ) {
xhr.onreadystatechange = null; xhr.abort = null; xhr = null;
}
|
With so many javascript memory leaks in Internet Explorer it can be very difficult to track down the cause and solution to any given leak. The last place I thought to look was in the most popular javascript library in the world. For pages that make a limited number of Ajax requests this leak is probably not an issue. But for any page that does continuous polling, or where a user can make numerous requests, it is most definitely a problem.
Stupid IE.
It’s been a while, but I think I’ve seen this before. IE doesn’t seem to always (or ever?) correctly break circular references between dom nodes and javascript objects. In this case, the xhr seems to be treated similarly so you have to break the loop manually.
Try adding:
cache: false
to the ajax options. Worked for me.
Just curious here… but have you tried your sample in a non-recursive way? Perhaps in a for loop rather than chaining the requests like you are.
Robby: I did not try a non-recursive test. I guess I could have used setInterval outside of the function instead of setTimeout inside of it. I don’t think it would make a difference for this issue though.
Was able to patch this without modifying jQuery itself. We actually wrap the jQuery ajax so it made it was pretty easy to handle and I’d recommend doing the same if you’re not already. Here is what it ended up looking like for us:
options.complete = function () {
var xhr = arguments[0];
// fix memory leaks in IEs
if (options.async === true || typeof async == ‘undefined’) {
if (typeof xhr != ‘undefined’) {
if (typeof xhr.onreadystatechange != ‘unknown’ ) {
xhr.onreadystatechange = null;
}
if (typeof xhr.abort != ‘unknown’ ) {
xhr.abort = null;
}
xhr = null;
}
}
if (typeof complete == ‘function’) {
complete.apply(this, arguments)
}
}
That snippet lives in our chunk of code which wrap $.ajax and just wraps the complete callback in code to null out the leaky stuff in the xhr object and then calls the original one if it exists.
Matt: Great solution. I think I will do something like this when jQuery 1.4.3 comes out, rather than patching the jQuery source itself.
jQuery 1.4.3 has been released.
This fix doesn’t seem to be in jQuery 1.4.3, right?
Philip: No, the fix is not in jQuery 1.4.3.
When I upgraded I went with a variation of the code in Matt Kaufman’s comment – basically wrapping the jQuery $.ajax call with my own function that sets the xhr properties to null on the “complete” handler.
Ok, could you give me an example?
Philip: Here is an example wrapper function:
So instead of calling $.ajax() you would call myAjax(), passing it any of the normal settings. This will work if you are always calling $.ajax in your code. If you call any of the convenience functions (e.g. $.get()) you will want to replace $.ajax with your wrapper.
I also just noticed a .ajaxComplete event handler. I have not tried this out, but it might be possible to do something like:
Ok thanks for that!
I am using ajaxManager for my requests: http://www.protofunc.com/scripts/jquery/ajaxManager/
I might have to tweak that one…
I have been struggling with this leak for a while. I have used the wrapper suggestion above but this still seems to leak for some reason. This all fires on Any ideas?
$(document).ready(function() {
var myAjax = function(settings) {
$.ajax($.extend({}, settings, {
complete: function (xhr, status) {
if (settings.complete) {
settings.complete(xhr, status);
}
// Avoid IE memory leak
xhr.onreadystatechange = null;
xhr.abort = null;
}
}));
};
function GetMyDataGrid() {
myAjax({
url: “includes/AjaxDataCheck/CheckForDataRefresh.cfm”,
cache: false,
dataType: ‘html’,
timeout: 5000, // 5 seconds,
data: “datacheck=DataGrid”,
success: function(result){
if (result.indexOf(“true”) >= 0) {
myAjax({
url: “includes/myinclude.asp”,
cache: false,
dataType: ‘html’,
timeout: 5000, // 5 seconds
data: “datacheck=MyVMs”,
success: function(data){
$(“#tab1response”).html(data);
}
});
}
}
});
};
setInterval(GetMyDataGrid,2000);
GetMyDataGrid();
});
Jeremy: First of all, jQuery 1.5 was just released and from what I have read the code that was causing this leak has been completely rewritten. I have not verified for myself that the memory leak has been fixed though.
As for the code you pasted in, I am not sure if something got messed up when you brought it into the comment box, but it looks like your “myAjax” function might be a little off. Setting the variables to null needs to take place in the “complete” callback. Here it is again slightly reworked to hopefully be a little easier to parse:
Patrick, thanks for the reply. There were a few things messed up when I pasted the code in. I used your reworked version and it seems at first glance to be working. I won’t know for sure until I let it sit for a while. I am keeping my fingers crossed. The weird thing is that I could have sworn I used xhr.onreadystatechange = null and was getting the the Type mismatch error from IE, but I don’t seem to be getting that now for some reason.
I was not aware that jQuery 1.5 had been released. I will go ahead and use that version as well.
I sure will be glad to get this resolved as many users like to leave this particular page open virtually all the time.
Thanks again for the response.
I had a web application that was leaking memory like mad in IE8. Started to suspect it was jQuery, and because of this page, I decided to see if upgrading to jQuery 1.5.2 would take care of the problem, and it completely went away. Thanks so much!
Michael: You are welcome. And thanks for confirming that 1.5.2 fixes the problem. That’s good to know going forward.
[…] — Ajax leak […]