Memory leak of first fulfillment handler closures
jbreckman opened this issue · 0 comments
I saved this as promisetest.js
and then ran node --inspect-brk promisetest.js
. I attached chrome's debugger to my node process and took a snapshot of the memory and it holds onto 20MB of memory for 100 seconds. (it's important to take a snapshot since that forces the GC to run).
I tested mostly on node 12, but the problem was identified in node 10.
const Promise = require('bluebird');
// any bluebird promise that doesn't resolve immediately will do.
let p = Promise.delay(5);
// create a fn that has a large data object closed inside of it
function createCbHandler() {
var lotsOfStrings = [];
const count = 333000;
for (var i = 0; i < count; i++) {
lotsOfStrings.push(String(Math.random()));
}
return () => {
return new Promise((resolve, reject) => {
resolve(lotsOfStrings[Math.floor(Math.random()*count)]);
})
}
}
// operate on the results of the promise, but retain the original promise reference
p.then(createCbHandler());
setTimeout(() => {
// make sure the original promise reference doesn't get garbage collected. It will hold onto 20MB of memory
// until this timer fires.
console.log('stop the process from quitting', p)
}, 100000);
Almost identical code, but with a no-op as the first fulfillment handler. This retains only 2MB of memory:
const Promise = require('bluebird');
// any bluebird promise that doesn't resolve immediately will do.
let p = Promise.delay(5);
// create a fn that has a large data object closed inside of it
function createCbHandler() {
var lotsOfStrings = [];
const count = 333000;
for (var i = 0; i < count; i++) {
lotsOfStrings.push(String(Math.random()));
}
return () => {
return new Promise((resolve, reject) => {
resolve(lotsOfStrings[Math.floor(Math.random()*count)]);
})
}
}
// have the first handler (_fulfillmentHandler0) be a noop, but retain the original promise reference
p.then(Object);
// operate on the results of the promise, but retain the original promise reference
p.then(createCbHandler());
setTimeout(() => {
// make sure the original promise reference doesn't get garbage collected. It will now hold onto
// only 2MB of memory
console.log('stop the process from quitting', p)
}, 100000)
I tried to narrow this down the best I could. I also tried to fix it --- but it looks like promise cancellation makes that very difficult. _fulfillmentHandler0 seems to be a special case that is retained as long as the promise itself is retained. The other fulfillment handlers get cleared out correctly.