Solving Memory Leaks in AJAX

I’ve been searching the web for a while now, and I haven’t come across a conclusive solution for memory leaks due to replacing nodes with frequent AJAX updates.

I wrote a class that pulls an RSS feed frequently (e.g. every 5 seconds) and updates an HTML element with new information. In the process, it removes the old nodes and replaces them with new ones. It works fine visually. The only problem is that it has terrible memory leak complications; when I put the script on to run, it increases the memory usage by about 1MB every few seconds at an accelerated 1000-ms delay between updates! (That’s 100MB every few minutes!) I discovered this when I let the script run for a few hours straight, not thinking anything of it, and when I returned to my computer, it was frozen up :eye: Opera seems to be the worst at its memory management on this script, Safari and Chrome are in the middle, and Firefox seems to handle it the best, but nonetheless there are memory leaks in all four. (I haven’t tested IE yet, but based on what I read, I would expect that it might even be worse than Opera!)

Here is my code:

var AJAXData = new Array();
function AJAXml(ajaxurl, ajaxcallback){
	var ajaxid;
	do{
		ajaxid = Math.random().toString();
	}while(AJAXData[ajaxid]);
	
	AJAXData[ajaxid] = getXMLReqObj();
	AJAXData[ajaxid].callback = ajaxcallback;
	
	AJAXData[ajaxid].onreadystatechange = function(){
		if(this.readyState==4 && this.status==200){
			this.callback(this.responseXML);
		}else if(this.readyState==4){
			window.status = "Error: "+this.statusText;
		}
	}
	
	AJAXData[ajaxid].open("GET", ajaxurl, true);
	AJAXData[ajaxid].send(null);
	
	return ajaxid;
}

function createFunctionReference(cfrobject, cfrmethod){
	var cfrfunc = function(){
		cfrmethod.apply(cfrobject, arguments);
	}
	return cfrfunc;
}

function RSSFeed(rssfref, rssfurl, rssftarget, rssfreloaddelay){
	this.url = rssfurl;
	this.target = rssftarget;
	this.delay = rssfreloaddelay;
	this.ref = rssfref;
	
	var rssLoadingInProgress = false;
	
	this.initRSS = function(irevt){
		this.target = document.getElementById(this.target);
		this.loadRSS();
	}
	
	this.loadRSS = function(lrevt){
		if(rssLoadingInProgress) return;
		rssLoadingInProgress = true;
		AJAXml(this.url, createFunctionReference(this, this.processRSS));
	}
	this.processRSS = function(prxml){
		rssLoadingInProgress = false;
		var pritems = prxml.getElementsByTagName("item");
		var prarr = new Array();
		for(var pri in pritems){
			if(!pritems[pri].nodeType) continue;
			/* pritems[pri].time is actually parsed and
			processed, but for sake of clarity, I'll post
			a generic substitution instead.  If it's
			important to see this function, let me know,
			and I can post the whole thing. */
			pritems[pri].time = {format:"Time formatted", collate=Math.random()};
			if(prarr.length==0){
				prarr.push(pritems[pri]);
			}else{
				var prscan = 0;
				while(prscan<prarr.length){
					if(prarr[prscan].time.collate < pritems[pri].time.collate){
						prarr.splice(prscan, 0, pritems[pri]);
						break;
					}
					prscan++;
				}
				if(prscan==prarr.length) prarr.push(pritems[pri]);
			}
		}
		
		/* This method goes through and "nullifies" the
		nodeValue of all child nodes (and children of
		children, and attributes) before removing them */
		this.target.clearChildNodes();
		var printnum = 0;
		for(var printnum=0; printnum<10; printnum++){
			var pritem = prarr[printnum];
			
			// A lot of document.createElement() stuff here, formatting the RSS for the user
			
			this.target.appendChild(pr_div);
		}
		prxml = null;
		setTimeout(this.ref+'.loadRSS();', this.delay);
	}
	
	document.addEventListener("DOMContentLoaded", createFunctionReference(this, this.initRSS), false);
}

// An example implementation:
var MyNews = new RSSFeed("MyNews", "newsfeed.php", "newsdiv", 5000);

What can be done to reduce memory leakage and not freeze my clients’ computers?

From the looks of it your cramming the AJAXData array with XMLHttpRequest (or similar) objects without destroying them when they’re finished.

I can think of three methods to solve this:

  1. Make sure to destroy the XMLHttpRequest once you don’t need it anymore
  2. Contain references to XMLHttpRequests within a function and don’t store them in the global scope and hope the JS Garbage Collector does the job it’s supposed to do
  3. Create only one XMLHttpRequest object and keep a flag whether it’s working or not. On each cycle check if the XMLHttpRequest is working and if it is, skip a request for that cycle

Option 3 has my personal preference, because then you can’t potentially create a humongous queue of objects waiting for data. What if the server that’s supposed to return the RSS feed is down? You’re sending out a request every 5 seconds so assuming the timeout of XMLHttpRequests is 30 seconds you’re running 6 parallel request at any given moment …

Hi ScallioXTX,

Thanks for the response!

Because the updates are on a timeout and not an interval (the last line of processRSS makes a new timeout), there would never be two requests running at the same time for the same RSSFeed instance, so I don’t have to worry about that part :cool:

I tried your option #1 and implemented it in my code:

function RSSFeed(rssfref, rssfurl, rssftarget, rssfreloaddelay){
	// …
	this.loadRSS = function(lrevt){
		if(rssLoadingInProgress) return;
		rssLoadingInProgress = true;
		this.currreq = AJAXml(this.url, createFunctionReference(this, this.processRSS));
	}
	this.processRSS = function(prxml){
		rssLoadingInProgress = false;
		AJAXData[this.currreq] = null;
		// …
	}
	// …
}

The AJAXml function already returns the index at which the request is contained in AJAXData. So, I store that information in “this.currreq”. When the callback, this.processRSS, is called, I go ahead and nullify the XMLHttpRequest since it won’t be needed any longer.

And lo and behold, this seems to solve the problem! Instead of Opera’s memory usage rising at .4MB per second, it raises at maybe .1MB per minute, which should be fine. Firefox barely shows any signs of memory leakage now, and same with Safari. Oddly enough, Chrome still shows signs of memory leakage, at about .2MB per second. Not sure why. However, it seems that Chrome does in fact “garbage collect” when the tab is put out of focus, and the memory usage resets to what it should be! Go figure.

Thanks again for your advice! It’s much appreciated! :smiley:

Try

delete AJAXData[this.currreq];

Hmm, or how about:

AJAXData[this.currreq] = null;
delete AJAXData[this.currreq];

Best of both worlds!