Neil's News

JSONP Memory Leak

27 July 2009

[Sorry, this is a technical post. Non-programmers should probably skip this and listen to some nice accordion music instead.]

Occasionally one has to defeat the same-origin policy and exchange data from a 3rd party server. The least ugly hack is the badly named JSONP technique. Basically you create a script tag, append it to head, then it will fetch a remote JavaScript file and execute it:

  script = document.createElement('script');
  script.src = 'http://example.com/cgi-bin/jsonp?q=What+is+the+meaning+of+life%3F';
  script.id = 'JSONP';
  script.type = 'text/javascript';
  script.charset = 'utf-8';
  // Add the script to the head, upon which it will load and execute.
  var head = document.getElementsByTagName('head')[0];
  head.appendChild(script);

This works well and can be executed at any time, not just at page load. As soon as the remote script is loaded it will execute will full permissions. Data can be sent to the server as in GET parameters on the script URL, and data can be received as JavaScript data structures in the returned file. One catch is that URLs have a maximum length, one that is not standardized. Thus if one wants to send more than about a thousand characters, then one needs to break the call into multiple requests.

When run once or twice, this is all fine. But when run repeatedly, the head will fill up with old script tags. Deleting them would appear to be easy:

  // Remove any old script tags.
  var script;
  while (script = document.getElementById('JSONP')) {
    script.parentNode.removeChild(script);
  }

The above code will successfully remove the script tags from the DOM. The problem is that every one of today's browsers will fail to garbage collect the offending node. The DOM and the JavaScript world have separate garbage collection algorithms and an entity which has one foot in both is often immortal since each side is scared of corrupting the other side. Thus the script tags stay in memory.

Since my application involves transmitting significant volumes of data through JSONP, the result was a browser leak in excess of 15 MB per hour. Leave a few of those tabs open overnight at your own risk. The solution was pretty simple, accept that the script tag will last forever, but destroy all its properties:

  // Remove any old script tags.
  var script;
  while (script = document.getElementById('JSONP')) {
    script.parentNode.removeChild(script);
    // Browsers won't garbage collect this object.
    // So castrate it to avoid a major memory leak.
    for (var prop in script) {
      delete script[prop];
    }
  }

A couple of lines of code saved many terabytes of user memory per day. Sweet.

As usual IE is the odd browser which requires a special case. IE doesn't like deleting native properties off of DOM nodes. Fortunately this doesn't matter, since IE allows one to reuse script tags. Just change the SRC property and it will fetch the new page immediately. Thus one only needs a single script tag in IE.

< Previous | Next >

 
-------------------------------------
Legal yada yada: My views do not necessarily represent those of my employer or my goldfish.