Hi there, I think there might be memory leaks from closures in widgets.js. I’ve done some profiling using Chrome Dev Tools which seem to point to leaks in the widgets.js code; what I’m seeing happens across browsers & platforms.
My application pulls in and displays tweets dynamically. It includes widgets.js in the HTML, retrieves embed snippets for tweets from the OEmbed API, inserts the snippets into the innerHTML of newly-created ‘div’ elements, and calls twttr.widgets.load(new_div). When it hits a configured limit, it removes (and nulls) the oldest tweet from the displayed list every time a new one is loaded. It also recursively nulls & removes the attributes and children (and their attributes) from the oldest tweet before it’s removed.
It all works well except that after a while it starts to eat up a lot of RAM. I tested loading it with lots of tweets by saving out the embed html from a bunch of tweets and just pushing them into the page, and leaving it running. After a while it eats all the RAM it can until the tab crashes. (Chrome, Safari, Firefox, the lot.)
After looking everywhere I could find in my code for leaks, I did a 6hr load/soak test with the twttr.widgets.load() call removed, and although the memory went up for a while, it eventually levelled out at around 60-70MB, and over time I could see it settling into cycles of a period of heap growth followed by GC runs dropping it back 10MB or so every so often. Unlike when the widget loader was being called, when it reliably shot up and up to 1GB and then just died.
So I did some profiling using Chrome Dev Tools and found what look like memory leaks in the widgets.js script. It was impossible to see what was going on from the minified version so I downloaded it, beautified it, and ran again, and I found a bunch of red nodes in detached DOM trees, which were pointed to via live yellow nodes in the widgets.js script.
Two examples are at http://imgur.com/nEG3Xkt and http://imgur.com/47z0x0i
The elements that appear to leak are :
- HTMLQuoteElement
- HTMLIFrameElement
- HTMLDivElement
- HTMLAnchorElement
- HTMLInputElement
- HTMLFormElement
- HTMLScriptElement
blockquote, iframe, div and a are all in the OEmbed HTML, and according to the Dev Tools are referred to by various functions (at lines 2130, 2564, 2562, 2560, 2558, 2556, 2566, 2553, 2550, 2531, 2530, 2529, 2528, 2527, 2524, 2521, 2496, 2493, 2474, 2471, 2466, 2460, 2451, 2499, 2487 of widget.js) in tfw/widget/base. Sure enough, at line 2564 (for example) there’s:
y.find = function (e) {
return e && p.byId[e] ? p.byId[e].element : null
}
or at line 2556:
y.afterLoad = function (e) {
d.push(e)
}
or at line 2499:
layout: function (e) {
return v.enqueue(e)
}
Etc. There are a bunch that Dev Tools calls out as holding references without even referring to the outer function’s variables, I guess because the closure creation just holds the context whether it’s accessed or not, e.g. at lines 2527-2530:
minWidth: function () {},
maxWidth: function () {},
minHeight: function () {},
maxHeight: function () {},
There are inner functions in many places in the code, so I’m guessing I’m only coming up against instances of this when it’s in functions I’m calling via twttr.widgets.load().
The input, form and script instances seem to be related to the “rufous-sandbox” element, or at least they’re referred to in inner functions in tfw/util/tracking. I can’t really see further into those latter references as the trace is buried in “native” sections.
Could someone verify this? Is there any way it could get fixed if so?
Thanks,
Igor