Timbersquirrel
Timbersquirrel

Reputation: 1

Possible memory leak with canvas npm package?

const { createCanvas } = require('canvas');

function renderImage() {
  const bgCanvas = createCanvas(800, 600);
  let bgCtx = bgCanvas.getContext('2d');
  bgCtx = null;
  global.gc();
}

console.log('Memory usage before:', process.memoryUsage());
renderImage()
console.log('Memory usage after:', process.memoryUsage());

The above code is a minimal code sample from a possible memory leak in my discord bot. It seems to be using anywhere between 20-40mb of memory with the .getContext() method and not giving it back. Any insight would be greatly appreciated since I am completely lost here.

I am using canvas ^2.11.2

This is the memory log:

Memory usage before: {
  rss: 35856384,
  heapTotal: 4665344,
  heapUsed: 4369736,
  external: 1323854,
  arrayBuffers: 10515
}
Memory usage after: {
  rss: 63238144,
  heapTotal: 5976064,
  heapUsed: 3500384,
  external: 1455306,
  arrayBuffers: 10475
}

Upvotes: 0

Views: 220

Answers (1)

Danil R.
Danil R.

Reputation: 41

UPD: Temporary solution: you can set your process manager (pm2 or whatever you are using) to automatically restart when a certain memory consumption is reached.

So, I've done some research and some tests and hope this information helps you:

First, you are using the library correctly - object references are not saved and therefore they will be collected by the garbage collector.

Now, about the Canvas library:

The getContext call adds a context to the parent Canvas object BUT this only happens on the first call to the function when the context object has not been created - subsequent calls to getContext use an already created object and are therefore not memory costly. I attach the function code from the source code of the canvas library:

node_modules/canvas/lib/canvas.js

Canvas.prototype.getContext = function (contextType, contextAttributes) {
  if (contextType == '2d') {
    const ctx = this._context2d || (this._context2d = new Context2d(this, contextAttributes))
    this.context = ctx
    ctx.canvas = this
    return ctx
  }
}

Most of the code of the 'Canvas' library is written in c++ where there is no garbage collector and memory management is done manually, so it is possible that a minor leak is hidden there. If memory leaks continue after the application is running (which I didn't get in my tests) - it makes sense to create an issue on the canvas github repository.

About GC(Garbage Collector)

To summarize - V8 Garbage Collector is a complex mechanism and it doesn't release memory immediately after removing object references, so:

  1. You don't need to use global.gc(), it works well in automatic mode
  2. You should not take memory usage immediately after deleting an object reference (as in your example).

Run tests on a running application that is actively running and not in “idle” mode

My tests result(Tests code)

Application started, RSS usage: 44.19584mbs
... (Canvas objects created)
RSS usage: 108.650496mbs, time since start: 2.5 minutes
... (Released a part of memory)
RSS usage: 79.773696mbs, time since start: 2.6 minutes
... (Released a part of memory)
RSS usage: 55.11168mbs, time since start: 32.3 minutes
... (Back to the original memory value)
RSS usage: 46.116864mbs, time since start: 63.4 minutes

Run code with --trace-gc flag, like node --trace-gc index.js for tracking GC operations

Upvotes: 0

Related Questions