Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

TL;DR -- all closures created within a function are retained even if only one closure is ever referenced. This is really interesting, and potentially devastating for certain coding styles.

I ran the code on node and the RSS does appear to be increasing without bound. Even running node with --expose-gc and forcing a global.gc() inside logIt() causes the same unbounded memory growth.

Increasing the size of the array by a factor of 10 causes RSS usage to jump up by a factor of 10 every second, so we know that the memory usage isn't caused by creating a new long-lived closure (i.e., the logIt() function) every second.

In fact, removing the call to doSomethingWithStr() doesn't change the unbounded memory growth.

Here's a shorter snippet that demonstrates the leak more dramatically (about 10 MB/sec RSS growth):

  var run = function () {
    var str = new Array(10000000).join('*');
    var fn  = function() {
      str;
    }
    var fn = function() { }
    setInterval(fn, 100);
  };
  setInterval(run, 1000);
Tried it out on node v0.8.18


To be clear: when you say "removing the call to doSomethingWithStr() doesn't change the unbounded memory growth" you do literally mean "removing the call" and not "removing the function definition", right? That matches what I see.


Correct. I'm actually a little surprised that an identity statement like "str;" isn't optimized out.

Edit: removing the function definition eliminates the problem, so I'd say that your diagnosis of the problem is spot on.


>> TL;DR -- all closures created within a function are retained even if only one closure is ever referenced. This is really interesting, and potentially devastating for certain coding styles. <<

This is not devastating, it is how closures and scope chain work in JavaScript. See my detailed explanation below. The crux of the matter is that the closure still references the outer function's scope chain, even after the outer function has returned. And the outer functions's scope chain is referenced by the closure until the closure is destroyed or returns.


I'm a very lazy (oops) language implementer. Every time but one when I've implemented closures, I took the quick approach of just copying the entire frame to the heap.

The other time, I was able to do more data flow analysis, but it also resulted in a bunch of annoying fiddly bugs and took more maintenance.

I'm not suggesting the v8 team took the 'easy way' out, but doing the deep introspection is hard, and in an environment such as a browser, I can see trading a pathological case such as this for what must be 1,000,000,000 inappropriate aggressive gc bugs.

(See the recent discussion over RubyMotion for examples of the opposite problem: http://news.ycombinator.com/item?id=5949072)


Note that the TL;DR may well be V8-specific; other engines optimize closures differently and may not have the same action-at-a-distance interaction between closures.

But the real killer is that you can't tell which closures will keep what alive unless you assume that every closure keeps everything it closes over (even if it doesn't use it) alive. Nothing in the ES spec requires them not to...


What is "RSS"?


After a nontrivial degree of Googling,

http://en.wikipedia.org/wiki/Resident_set_size


It was an honest question. Thanks for the downvote moron, very helpful.


I'm very confused by this whole thread.

Is it that, only one closure is created for variables defined in the function run, and variables needed in any inner-function are added to run's closed context? That seems like a bug fixable without impacting the language.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: