3/28/2007

Stopping the memory leaks with Prototype

Lately, I've been trying to fix the memory leaks in my application. I found this cool little tool for Firefox: Leak Monitor. It's pretty nice at showing you when you have a leak, but it could be improved a lot more by showing you more details about the leaks. Right now it only tells you the creation point of the object that was leaked. Which if you're using a javascript library like Prototype then you get a lot of references to code you didn't write. It would be more helpful if it could print out the complete stack trace that caused the construction. And it would be nice if you could turn the plugin on and off for given pages. Right now it alerts you to every leak which is interesting to see what pages you visit the most cause your browser to leak *cough* Digg, gmail. But, ultimately it's annoying as all hell after a few visits. Bottom line it's still a nice tool.

What I found in my code was lots of problems using bind() and bindAsEventListener(). Massive amounts of memory being wasted by yours truly because I didn't clean up my event handlers. But, I thought Prototype handled this for me judge! Honest! I started reading the new prototypejs.org site for any hint of documenation mentioning cleaning up references. Prototype will install a listener on the unload event and cleanup all listeners registered through Event.observe(). But, reading the fine print it only does this if you're in IE! But, I'm seeing tons of memory leaked in Firefox. Why not do it for Firefox as well? It's all the same javascript, and if I have to go around and manage all of the listeners myself what's the point of having IE then do it for me? Only the Prototype developers know why. But, here is what I did to fix my problems:


Event.observe( document, 'unload', Event.unloadCache );


Put that in my javascript and viola I went from over 70-80 leaked objects down to 2. I'm so smart...hey wait a minute two! So I checked my leak monitor tool, and it's coming from the bind() method in prototype. Well I'm a heavy user of the bind() method so it could be anywhere. At first I thought maybe it was Behaviour so I removed Behaviour, but nope still 2 being leaked.

Fingers drumming, mouth frowning, what in the heck could it be?! I played around and noticed sometimes I leaked 1 object. Other times I leaked 4. Hmmm. Then I remembered something that I read on Jack Slocum's blog 3 Easy steps to avoid javascript memory leaks. Number 3 relates to using Ajax calls. So I commented out my Ajax call I was doing on page load and viola no leaks! Looks like my use of bind() method there was causing trouble. So now I know what it is, but what's is Prototype not doing?

After hunting through the Prototype source I see where it's setting the onreadystatechange to null. I've been working on it for a couple of days, and I still can't figure out why it's leaking. This is where it's unclear why my object is being leaked. Comparing this with how you look for leaks in Java this is a real pain. In order to implement garbage collection you have to have what's called a root set. This is a set of objects that is considered always live like global variables live forever, or the client side dom. The garbage collection algorithm starts recursively walking object references from the root set marking the objects as reachable. Once it's finished visiting all the reachable objects it can throw away the objects that aren't reachable. So essentially anything that is reachable from the root set will survive garbage collection. To clean up objects simply cut all the references from the live set. That means for a given object we ought to be able to see what objects have references to it, and what objects have reference to those, etc. Now I know that because IE uses ref counting it can leak objects not reachable from the root set, but every other browser out there implements a real GC algorithm. And remember I'm talking about tools that run in Firefox mainly.

Leak monitor just tells you if you're leaking memory, it doesn't tell you why. What we need is a tool that traces the objects back to the root set so it can show us where it's being referenced accidentally. Then we can cut that reference and move on. A good tool like this would make leak removal makes this process easy for anyone to do. Right now it's much more of a hunt and guess that takes forever.

Well I guess it's back to hunting.