Memory leak in nodejs promise design?

2019-08-15 17:27发布

问题:

I noticed some people here recommend to use await/async and promise instead of setTimeout directly when you want to delay the execution of something. This is the code:

async wait (ms){
  return new Promise(resolve => setTimeout(resolve, ms));
}

So I would use

await wait(3000);
my_function();

instead of

setTimeout(() => {
  my_function();
}, 3000);

It makes sense but I noticed that if I do that I get increased memory usage and eventually the app crashes with heap of out memory after a few hours.

Is this an issue in the promise design of nodejs, or am I missing something here?


This code reproduces the issue:

const heapdump = require('heapdump'),
      fs = require('fs');
class test {
  constructor(){
    this.repeatFunc();
  }
  async wait (ms){
    return new Promise(resolve => setTimeout(resolve, ms));
  }
  async repeatFunc(){ 
    // do some work...
    heapdump.writeSnapshot(__dirname + '/' + Date.now() + '.heapsnapshot');    

    await this.wait(5000); 
    await this.repeatFunc();
  }
}
new test();

Notice heap dump keeps increasing every 5 seconds

With setInterval this doesn't happen:

const heapdump = require('heapdump'),
      fs = require('fs');
class test {
  constructor() {
    setInterval(this.repeatFunc, 5000);
  }
  repeatFunc() { 
    // do some work...
    heapdump.writeSnapshot(__dirname + '/' + Date.now() + '.heapsnapshot');    
  }
}
new test();

回答1:

You have written an infinitely recursive function, and each function call returns a new promise. Each promise is waiting to be resolved from the inner one - so yes, it of course is accumulating memory. If the code was synchronous, you would have gotten a stack overflow exception instead.

Just use a loop instead:

const heapdump = require('heapdump'),
      fs = require('fs');

async function wait(ms){
  return new Promise(resolve => setTimeout(resolve, ms));
}
async function repeat() {
  while (true) {
    // do some work...
    heapdump.writeSnapshot(__dirname + '/' + Date.now() + '.heapsnapshot');    

    await wait(5000); 
  }
}

repeat().then(() => console.log("all done"), console.error);

I noticed some people here recommend to use await/async and promise instead of setTimeout directly when you want to delay the execution of something.

Well that includes me, as promises are much easier to work with, especially when you want to return a result value from your asynchronous task or handle errors. But if you're not convinced by any of the advantages of promises, there is nothing that forces you convert your already-working code to promises. Just carry on using callback style until you find a good use for promises.



回答2:

To avoid memory leaks within a promise it's a good idea to clear all your timeouts after you use them.

function wait(delay) {
    return new Promise(function(resolve) {
        let id = setTimeout(function() { resolve(); clearTimeout(id);  }, delay);
    });
}

(async () => {
   await wait(2000);
   alert("Test");
})();