Search code examples
node.jsmemory-leakssails.jsv8

Does v8/Node actually garbage collect during function calls? - Or is this a sailsJS memory leak


I am creating a sailsJS webserver with a background task that needs to run continuously (if the server is idle). - This is a task to synchronize a database with some external data and pre-cache data to speed up requests.

I am using sails version 1.0. Tthe adapter is postgresql (adapter: 'sails-postgresql'), adapter version: 1.0.0-12

Now while running this application I noticed a major problem: it seems that after some time the application inexplicably crashes with an out of heap memory error. (I can't even catch this, the node process just quits).

While I tried to hunt for a memory leak I tried many different approaches, and ultimately I can reduce my code to the following function:

async DoRun(runCount=0, maxCount=undefined) {
  while (maxCount === undefined || runCount < maxCount) {
    this.count += 1;
    runCount += 1;
    console.log(`total run count: ${this.count}`);
    let taskList;
    try {
      this.active = true;
      taskList = await Task.find({}).populate('relatedTasks').populate('notBefore');
      //taskList = await this.makeload();
    } catch (err) {
      console.error(err);
      this.active = false;
      return;
    }
  }
}

To make it "testable" I reduced the heap size allowed to be used by the application: --max-old-space-size=100; With this heapsize it always crashes about around 2000 runs. However even with an "unlimited" heap it crashes after a few (ten)thousand runs.

Now to further test this I commented out the Task.find() command and implimented a dummy that creates the "same" result".

async makeload() {
  const promise = new Promise(resolve => {
    setTimeout(resolve, 10, this);
  });
  await promise;
  const ret = [];
  for (let i = 0; i < 10000; i++) {
    ret.push({
      relatedTasks: [],
      notBefore: [],
      id: 1,
      orderId: 1,
      queueStatus: 'new',
      jobType: 'test',
      result: 'success',
      argData: 'test',
      detail: 'blah',
      lastActive: new Date(),
      updatedAt: Date.now(),
      priority: 2 });
  }
  return ret;
}

This runs (so far) good even after 20000 calls, with 90 MB of heap allocated. What am I doing wrong in the first case? This let me to believe that sails is having a memory leak? Or is node unable to free the database connections somehow?

I can't seem to see anything that is blatantly "leaking" here? As I can see in the log this.count is not a string so it's not even leaking there (same for runCount).

How can I progress from this point?


EDIT Some further clarifications/summary:

  • I run on node 8.9.0
  • Sails version 1.0
  • using sails-postgresql adapter (1.0.0-12) (beta version as other version doesn't work with sails 1.0)

I run with the flag: --max-old-space-size=100
Environment variable: node_env=production

It crashes after approx 2000-2500 runs when in production environment (500 when in debug mode).


I've created a github repository containing a workable example of the code; here. Once again to see the code at any point "soon" set the flag --max-old-space-size=80 (Or something alike)


Solution

  • I don't know anything about sailsJS, but I can answer the first half of the question in the title:

    Does V8/Node actually garbage collect during function calls?

    Yes, absolutely. The details are complicated (most garbage collection work is done in small incremental chunks, and as much as possible in the background) and keep changing as the garbage collector is improved. One of the fundamental principles is that allocations trigger chunks of GC work.

    The garbage collector does not care about function calls or the event loop.