6

This is something weird I noticed. The following code shouldn't blow the memory as a WeakSet is used and obviously no other references linger around:

'use strict';
require('babel-polyfill');

const s = new WeakSet();

for (let i = 0 ; ; i++) {
    s.add({});
    if (i % 100000 === 0)
        console.log(`${i} :${process.memoryUsage().heapUsed}`);
}

(SCCE github repo here).

And yet blow the memory it does (in Node v4.3.2 with Babel transpiling):

<--- Last few GCs --->

 165 ms: Scavenge 13.6 (48.0) -> 13.6 (48.0) MB, 14.4 / 0 ms   [allocation failure].
 189 ms: Scavenge 14.4 (48.0) -> 14.4 (52.0) MB, 17.6 / 0 ms [allocation failure].
 340 ms: Scavenge 37.5 (68.0) -> 37.5 (68.0) MB, 35.2 / 0 ms [allocation failure].
 380 ms: Scavenge 38.3 (68.0) -> 38.3 (76.0) MB, 35.5 / 0 ms [allocation failure].
 567 ms: Scavenge 53.5 (76.0) -> 53.4 (77.0) MB, 74.6 / 0 ms [allocation failure].


<--- JS stacktrace --->

==== JS stack trace =========================================

Security context: 0x228b1a4b4629 <JS Object>
     1: add [native weak-collection.js:~92] [pc=0x2b4d202650b5]   (this=0x386dbd0641f9 <JS WeakSet>,l=0x389216b5e19 <an Object with map 0x21f1c4616e79>)
     2: /* anonymous */ [/home/mperdikeas/weak-set-blows-memory/es5/app.js:~1] [pc=0x2b4d20269023] (this=0x386dbd064221 <an Object with map 0x3193b8408829>,exports=0x228b1a4041b9 <undefined>,require=0x228b1a4041b9 <undefined>,module=0x228b1a4041b9 ...

 FATAL ERROR: invalid table size Allocation failed - process out of memory
 Aborted (core dumped)

 npm ERR! Linux 3.16.0-48-generic
 npm ERR! argv "/usr/bin/nodejs" "/usr/bin/npm" "run" "start"
 npm ERR! node v4.3.2
 npm ERR! npm  v2.14.12
 npm ERR! code ELIFECYCLE
 npm ERR! simple-babel-serverside-node-only-archetype@1.0.0 start: `node es5/app.js`
 npm ERR! Exit status 134
 npm ERR! 
Marcus Junius Brutus
  • 26,087
  • 41
  • 189
  • 331
  • So it transpiles to ES5, running in an infinite loop, adding an empty object to `WeakSet`, forever. I don't know what you expected really (didn't downvote you though) :) – Mjh Apr 08 '16 at 15:34
  • I wasn't expecting to get anything useful out of this. This was an experiment or a SSCCE if you like. Since there are no other references to the objects being added I was expecting GC to kick in. – Marcus Junius Brutus Apr 08 '16 at 15:35
  • Well then, it's not weird, is it? You don't have bottomless memory, yet you're trying to add stuff to it forever. Can't work in this universe, can it? :) – Mjh Apr 08 '16 at 15:36
  • 1
    _... shouldn't blow ..._ ye, I too would expect that GC kicks in but the behavior is actually not wrong. A [weak reference](https://en.wikipedia.org/wiki/Weak_reference) **may** be seen as trash. It does not imply that it must be freed on memory shortage. – makadev Apr 08 '16 at 15:45
  • Weak-collections were introduced in order to avoid memory leaks and what's the purpose of GC if not avoiding memory shortage. So this is a completely legitimate question. –  Apr 09 '16 at 10:42
  • It seems to be a bug in v8, explicitly running gc works – vkurchatkin Apr 11 '16 at 00:49
  • this seems like a good read http://stackoverflow.com/a/30556242/646156 .It explicitly states you should not add empty objects, but indeed I suppose the GC should kick in anyway. – Tudor Ilisoi Apr 12 '16 at 17:42

2 Answers2

7

Update The bug has been fixed today.The fix is moving to v8 5.0 which is what Node 6.0 is using - so in a few weeks you'll have a Node version that has it fixed.


This is an open bug in v8. Your code as stated should work just fine. The problem is basically that v8 doesn't do full garbage collection but only minimal garbage collection in this case.

This is not working fine in Chrome, the only reason it is not leaking in Chrome is because other objects are created and can be freed - those objects being possible to free triggers full garbage collection which also cleans the WeakSet.

WeakSet comes natively, there is a clever polyfill for it in core-js but it is not actually 100% weak, used here or is relevant.

Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
3

Following code doesn't leak in Chrome 49:

'use strict';

const s = new WeakSet();
let j = 0;
function f(){
    for (let i = 0 ; i<100000; i++) {
        s.add({});  
    }
    console.log(`${j++} :${0|(performance.memory.usedJSHeapSize/(1024*1024))}MB`);
    return Promise.resolve(null).then(f);
}
f()

So it seems like a bug in Node.js (it's reproducible in v5.1.10 too). I have filled an issue for this in the Node.js bugtracker.

Ginden
  • 5,149
  • 34
  • 68