AJAX in jQuery leaks memory, really, is that true?

Difficult to believe, but yes AJAX in jQuery version 1.4.2 does leak. I had a nasty suspicion about this and a reported bug 6242 confirmed it. Not being comfortable with taking the recommended fix at face value I decided to look into this myself.

Before I continue I want to encourage you to read my previous posts XMLHttpRequest Leak in IE 7/8 and xmlhttprequest-leak-in-ie-78-forgot-the-abort-thing to fully understand my reasoning in this post.

To test the severity of the leak I wrote a simple test.

test-jquery-ajax-leak6.html

<html>
<head>
   <title>jQuery Ajax Leak Demo</title>
   <script src="jquery-1.4.2.js"></script>
<script>
$(document).ready(function(){

   //no caching of calls to for better accuracy
   $.ajaxSetup({cache: false});
   
   var interval;
   var i = 1000; //number of calls
   
   $('#button1').click(function(){
     interval = setInterval(makeLeak, 50); 
   });
   
   
   function makeLeak(){
      $.get('test6.php', function(){
          if(--i === 0){
            //all calls done. Cleanup
               clearInterval(interval);
               interval = null;
               alert('All Done');
            }
      });
   };

});
 
</script>
  
</head>

<body>
   <button id="button1" >Fire</button>
</body>
</html>

Nothing fancy here. 1000 calls at interval of 50ms. Also, the test6.php contains just a dummy echo:

test6.php

<?php echo ''; ?>

If I run the html file in sIEve I get this:

Only making the AJAX calls and doing zero data processing the memory consumption increased from 15,600 bytes to 41,428 bytes! More than 2.5 times. For 10,000 iterations the memory went up from 15,680 to 257,208 bytes. We have a leak!

To see what was going on I dissected the jQuery AJAX code. For sake of clarity I have removed code not relevant to this discussion. The pared down code from ‘jquery-1.4.2.js’ looks like this:

ajax: function( origSettings ) {

		var requestDone = false;

		// Create the request object
		var xhr = s.xhr();

		if ( !xhr ) {
			return;
		}

		// Open the socket
		// Passing null username, generates a login popup on Opera (#2865)
		if ( s.username ) {
			xhr.open(type, s.url, s.async, s.username, s.password);
		} else {
			xhr.open(type, s.url, s.async);
		}

		// Wait for a response to come back
		var onreadystatechange = xhr.onreadystatechange = function( isTimeout ) {
			// The request was aborted
			if ( !xhr || xhr.readyState === 0 || isTimeout === "abort" ) {
				//this code removed

                        requestDone = true;
				if ( xhr ) {
					xhr.onreadystatechange = jQuery.noop;
				}

			// The transfer is complete and the data is available, or the request timed out
			} else if ( !requestDone && xhr && (xhr.readyState === 4 || isTimeout === "timeout") ) {
				requestDone = true;
				xhr.onreadystatechange = jQuery.noop;
                                
                                //fire success callback
				success();

                                //fire complete callback
                                complete();

                                //more code removed here

				if ( isTimeout === "timeout" ) {
					xhr.abort();
				}

				// Stop memory leaks
				if ( s.async ) {
					xhr = null;
				}
			}
		};

		// Override the abort handler, if we can (IE doesn't allow it, but that's OK)
		// Opera doesn't fire onreadystatechange at all on abort
		try {
			var oldAbort = xhr.abort;
			xhr.abort = function() {
				if ( xhr ) {
					oldAbort.call( xhr );
				}

				onreadystatechange( "abort" );
			};
		} catch(e) { }

      	// Send the data
		try {
			xhr.send( type === "POST" || type === "PUT" || type === "DELETE" ? s.data : null );
		} catch(e) {
			jQuery.handleError(s, xhr, null, e);
			// Fire the complete handlers
			complete();
		}

		// return XMLHttpRequest to allow aborting the request etc.
		return xhr;
	}


Stepping through the function:

1. An instance of the ‘XMLHttpRequest’ object is created and put in variable named ‘xhr’.
2. the ‘open’ method is executed in preparation for ‘send’.
3. A handler is defined for the callback ‘xhr.onreadystatechange’ .
4. The ‘abort’ method is over-ridden.
5. The ‘send’ request is made.
6. ‘xhr’ the reference to the ‘XMLHttpRequest’ object instance is returned.

When the call returns it fires the ‘xhr.onreadystatechange’ handler. The following sequence of event takes place:

1. Assuming that the call is complete(readyState == 4) the else part of the if is executed.
2. To prevent leaks the handler is cleaned up like so: xhr.onreadystatechange = jQuery.noop;. (jQuery.noop is a jQuery no-operation function and is defined as noop: function() {}(~Line 520))
3. The success callback handler is called.
4. The complete callback handler is called.
5. If there is a timeout call is aborted.
6. Finally to avoid leaks xhr is set to null. (See my previous post to see why)

So, if the ‘xhr.onreadystatechange’ callback handler is being cleaned up and ‘xhr’ is being set to null why is there a leak? It is because the ‘abort’ method is over-ridden but not cleaned up. To do that we need to replace(around line 5220):

// Stop memory leaks
if ( s.async ) {
xhr = null;
}

with

// Stop memory leaks
if ( s.async ) {
xhr.abort = jQuery.noop;
xhr = null;
}

Let’s run the test again:

As you can see the consumption has stabilized around 18K. Also note how the memory is being released(-green) as the number of calls progress.

To my satisfaction I also found the official fix

Happy computing!

XMLHttpRequest Leak in IE 7/8, forgot the abort thing!

After I was done writing my previous post XMLHttpRequest Leak in IE 7/8 I suddenly remembered that I had missed the ‘abort’ method of ‘XMLHttpRequest’.

It means what it says:-) It aborts the current call.

Many a times I want to do some housekeeping either before or after calling the ‘abort’ method. To make the housekeeping transparent I will usually end up over-riding ‘abort’ before exposing it. Many libraries do it including jQuery. Consider the following code:

<html>
<head>
	<title>xmlHttpRequest Leak, Abort Override Demo</title>
<script>

function makeAjaxCall(){
    var xhr = new XMLHttpRequest();
    xhr.open('GET', 'test1.php', true);
    xhr.onreadystatechange = function (event){
      if(xhr.readyState == 4 && xhr.status == 200){
         //clean up to avoid leaks
         xhr.onreadystatechange = new Function;
         xhr = null;
      }
   };
   
   //override abort here
   var oldAbort = xhr.abort;
   xhr.abort = function() {
   	if ( xhr ) {
   		oldAbort.call( xhr );
   	}
      //some housekeeping code
      //-----
      //-----
   	
   };
	
   xhr.send(null);
   return xhr;
} 


var xhr = makeAjaxCall();

//To abort I would call
xhr.abort();
  
</script>
  
</head>

<body>
</body>
</html>

Note that I have over-ridden the abort method. Nothing fancy or tricky here.

Just to make sure everything is OK I am going to create a test to check for any leaks. Here is the test case you are probably familiar with by now:

leak-test4.html

<html>
<head>
	<title>xmlHttpRequest Leak, Abort Override Demo</title>

<script>

var interval;
var i = 1000;
function fire(){
  interval = setInterval(makeLeak, 50); 
} 

function makeLeak(){
    var xhr = new XMLHttpRequest();
    xhr.open('GET', 'test1.php?i='+i, true);
    xhr.onreadystatechange = function (event){
      if(xhr.readyState == 4 && xhr.status == 200){
         //cleanup to avoid leaks
         xhr.onreadystatechange = new Function;
         xhr = null;
            if(--i === 0){
               clearInterval(interval);
               interval = null;
               alert('All Done');
            }
      }
   };
   
   
   var oldAbort = xhr.abort;
   xhr.abort = function() {
   	if ( xhr ) {
   		oldAbort.call( xhr );
   	}
      //housekeeping code
      //----
      //------
   	
   };
	
   xhr.send(null);
   
   return xhr;
} 


  
</script>
  
</head>


<body>
   <button id = "button1" onclick="fire(false);">Fire</button>
</body>
</html>

Running this in sIEve produces the following:

IE Leak Abort
IE Leak Over-riding Abort

It seems there is a leak. I confirmed that I had cleaned up by putting the following code in the ‘xhr.onreadystatechange’ callback handler. What went wrong now?

xhr.onreadystatechange = new Function;
xhr = null;

Well I did not do a thorough job of cleaning up!. It seems I forgot to use my own advice. Like I mentioned in my previous post, you need to clean up functions you over-ride. I need to clean up ‘abort’ by adding the following:
xhr.abort= new Function;

My ‘xhr.onreadystatechange’ handler will now look like this:

xhr.onreadystatechange = function (event){
      if(xhr.readyState == 4 && xhr.status == 200){
         //cleanup to avoid leaks
         xhr.onreadystatechange = new Function;
         xhr.abort = new Function; //clean up abort override
         xhr = null;
            if(--i === 0){
               clearInterval(interval);
               interval = null;
               alert('All Done');
            }
      }
   };

Having done that, this is what I get from sIEve

IE Leak Abort Fig 2
IE Leak Over-riding Abort Fig 2

Notice that the leak has stopped.

Remember, always cleanup after yourself.

Happy computing!

XMLHttpRequest Leak in IE 7/8

Recently I came across a leak in a legacy IE 7/8 application. Memory was building up proportionally with the increasing frequency of AJAX calls. Further research brought me to this page Internet Explorer: memory leak in XMLHttpRequest (on-page) where to my surprise I found the reason for the leaks.

Sergey Ilinsky, the author of the page notes:

“Bug: The instance of XMLHttpRequest doesn’t get garbage collected in case you have a reference to the instance or to an other COM object (for example: DOM Node etc.) in its onreadystatechange handler, thus producing runtime memory leaks. “

He has also proposed a fix. I will talk about that in a moment.

To measure the leak here is a simple test page, test-leak1.html.

test-leak1.html

<html>
<head>
	<title>xmlHttpRequest Leak Demo I</title>

<script>

var interval;
var i = 1000;
function fire(){
  interval = setInterval(makeLeak, 50);
} 

function makeLeak(){
    var xhr = new XMLHttpRequest();
    //open ajax call. 'i' is dummy character to avoid caching of requests
    xhr.open('GET', 'test1.php?i='+i, true);
    xhr.onreadystatechange = function (event){
      if(xhr.readyState == 4 && xhr.status == 200){
            //all iterations done...stop timer and cleanup
            if(--i === 0){
               clearInterval(interval);
               interval = null;
               alert('All Done');
            }
      }
   };
   xhr.send(null);

}
</script>
</head>

<body>
   <button id = "button1" onclick="fire();">Fire</button>
</body>
</html>

The setup is extremely simple where after the ‘Fire’ button is clicked 1000 AJAX calls are made at 50ms intervals.

The test1.php contains on an echo statement. The intent being that I don’t want to do any data processing.

<?php
echo '';
?>

Here are the results when you run ‘test-leak1.html’ in ‘sIEve’:

IE Leak Test Fig 1
IE Leak Test Fig 1

Note that memory consumption grows from 13748 bytes to 31764 bytes. More than doubled and we haven’t even processed any data or manipulated DOM elements. You can very well imagine the damage this can do in an intensive AJAX application.

So, what is causing the leak? Let’s dissect the code to understand:

  1. 1. ‘makeLeak’ function is called.
  2. 2. An instance of ‘XMLHttpRequest’ is created and its reference put in the variable ‘xhr’.
  3. 3. The ‘open’ method is called to prepare the send request. Note the request is not sent yet.
  4. 4. ‘xhr.onreadystatechange’ callback is defined
  5. 5. The request is now sent using the ‘send’ method.
  6. 6. ‘makeLeak’ function ends and goes out of scope.

Form the above we can draw the following logical observations:

  1. 1. ‘makeLeak’ is the outer function.
  2. 2. ‘xhr.onreadystatechange’ is the inner function.
  3. 3. The inner function(‘xhr.onreadystatechange’) has access to the ‘xhr’ variable defined in its outer scope. This is true even though the outer function has gone out of scope! Think of ‘xhr’ as a static variable.
  4. 4. This implies that the outer function(‘makeLeak’) closes over the inner function ‘xhr.onreadystatechange’. Hence ‘makeLeak’ is a ‘closure’.

When the AJAX call returns:

  1. 1. ‘xhr.onreadystatechange’ fires.
  2. 2. A check is made if the AJAX call is successful(xhr.readyState == 4 && xhr.status == 200).
  3. 3. If it is not done it returns. Note that the outer variable ‘xhr’ still holds a valid reference to the ‘XMLHttpRequest’ object.
  4. 4. If it is successful(xhr.readyState == 4 && xhr.status == 200) then as far as IE is concerned the AJAX call is done.
  5. 5. When the garbage collector comes around it finds the inner function still referencing ‘xhr’ in the outer function’s scope. It leaves it alone.
  6. 6. So every time ‘makeLeak’ is called, a new instance of XMLHttpRequest is created and orphaned.

But before we start fixing this leak let’s confirm that XMLHttpRequest is still alive in well even after everything is over. Consider the following code:

test-leak3.html

<html>
<head>
<title>xmlHttpRequest Leak Demo I</title>

<script>
var success = false;

function makeLeak(){
   console.log('In outer function scope');
    var xhr = new XMLHttpRequest();
    xhr.open('GET', 'test2.php', true);
    xhr.onreadystatechange = function (event){
      if(xhr.readyState == 4 && xhr.status == 200){
         console.log('In inner function scope(AJAX Success). Response text: ' + xhr.responseText);
         success = true;
      }
   };
   xhr.send(null);

   //return reference to xhr
   var getXhr = function(){
      return xhr;
   }
   //return the reference to the function getXhr
   return getXhr;
}   

//call function makeLeak. starts the AJAX process and returns a reference
//to the method getXhr. In other words g() = makeLeak.getXhr()
var g = makeLeak();

//set up a timer to check the state of 'xhr' after the AJAX call completes.
var interval = setInterval(checkXhr, 1000);

function checkXhr(){
   if(success === true){
      //get the value of xhr;
      var xhr = g();
      if(xhr){
         console.log('xhr.responseText: ' + xhr.responseText);
      }else{
         console.log('xhr.responseText: ' + xhr);
      }
      //cleanup
      clearInterval(interval);
      interval = null;
   }
}
</script>
</head>

<body>
</body>
</html>

and test2.php contains:

<?php
echo 'Hello';
?>

The intent is to check the state of the ‘XMLHttpRequest’ object after the AJAX call is complete.

The ‘makeLeak’ function defines two inner functions. One is the callback ‘xhr.onreadystatechange’ and the other is ‘getXhr’. ‘getXhr’ only returns the handle to the ‘XMLHttpRequest’ object.

I have also defined a new variable ‘success’ . Once the AJAX call is successful it is set to ‘true’. The interval handler checks for the ‘success’ flag and if true tries to get the handle to the XMLHttpRequest object(xhr in makeLeaK). Once it has that it displays ‘responseText’.

Let’s check it out. After running the file the console windows shows the following:
Console [20]=
In outer function scope

Console [21]=
In inner function scope(AJAX Success). Response text: Hello

Console [22]=
xhr.responseText: Hello

Notice that even though ‘makeLeak’ has gone out of scope its internals are preserved. The bottom line is that the garbage collector will not cleanup an object if it determines that there is a reference to the object. So we need to help out. Please consider the following code in test-leak2.html.

test-leak2.html

<html>
<head>
	<title>xmlHttpRequest Leak Demo I</title>

<script>

var interval;
var i = 1000;
function fire(){
  interval = setInterval(makeLeak, 50);
} 

function makeLeak(){
    var xhr = new XMLHttpRequest();
    //open ajax call. 'i' is dummy character to avoid caching of requests
    xhr.open('GET', 'test1.php?i='+i, true);
    xhr.onreadystatechange = function (event){
      if(xhr.readyState == 4 && xhr.status == 200){
            //make object null, help the garbage collector to clean up
            xhr.onreadystatechange = new Function; //empty function
            xhr = null;
            //all iterations done...stop timer and cleanup
            if(--i === 0){
               clearInterval(interval);
               interval = null;
               alert('All Done');
            }
      }
   };
   xhr.send(null);

}
</script>
</head>

<body>
   <button id = "button1" onclick="fire();">Fire</button>
</body>
</html>

The only difference between this file and ‘test-leak1.html’ is these two lines in the ‘xhr.onreadystatechange’ handler:

xhr.onreadystatechange = new Function; //empty function
xhr = null;

Only xhr = null would also work but it is always a good practice to cleanup all that you have defined. In this case we defined the callback so replace it with an empty function.

Now if I run test-leak2.html in sIEve here is what I get:

IE Leak Fig 2
IE Leak Fig 2

There is virtually no memory buildup and it seems to have stabilized around 15,400 bytes.

I hope reading this post was as educational for you as it was for me writing it.

Happy computing!