Automattic / Automattic/node-canvas

Severe memory leak when rendering text repeatedly and using `deregisterAllFonts()`

Open
#1,974 7 comments 3 reactions 0 assignees View on GitHub
Text & Fonts
Dominant language
JavaScript
Stars
10.7k
Forks
1.2k
Avg merge
4d 8h
Merged PRs (30d)
1

Description

There appears to be a rather severe memory leak when rendering text repeatedly, and calling `deregisterAllFonts()` and `registerFont()` between each iteration.

## Steps to Reproduce

Here is a simple script to reproduce and show the leak:

```js
// This test demonstrates a memory leak when repeatedly registering, rendering, and deregistering a font.
// Run the script and look at the memory output, specifically the "rss" (resident memory) reading.

const Canvas = require('canvas');

console.log( "MEM BEFORE: ", process.memoryUsage() );

for (var idx = 0; idx < 2500; idx++) {
Canvas.registerFont('futurastd-m.otf', { family: 'FuturaStdMedium' });

var canvas = Canvas.createCanvas(640, 100);
var ctx = canvas.getContext('2d');
var text = 'Now is the time for all good men.';

ctx.font = 'normal normal 36px FuturaStdMedium';
ctx.fillText(text, 10, 35);

var buf = canvas.toBuffer();

buf = null;
ctx = null;
canvas = null;

Canvas.deregisterAllFonts();
}

console.log( "MEM AFTER: ", process.memoryUsage() );
```

After running 2,500 iterations in a single process, the `rss` memory is upwards of 400MB on Linux, and 4GB on macOS.

I do realize that rendering text and producing PNG images takes some memory, but this seems to continually leak the longer it runs.

I have grabbed multiple JS heap dumps and compared them in Chrome Dev Tools. The memory is definitely not in Node.js land, so it must be on the C++ side of things.

## Your Environment

- Reproducible on both Linux (CentOS 7.7) and macOS (Monterey 12.1), using Node.js 16.13.0.
- Reproducible in both node-canvas v2.9.0 with precompiled binaries, and latest HEAD revision using source compile.
- Reproducible using both OTF and TTF fonts.
- Interestingly **not** reproducible if the script simply calls `registerFont()` and `deregisterAllFonts()` without creating and drawing into a canvas.

Here is a ZIP file containing the script to reproduce, and the FuturaStdMedium OTF font:

https://pixlcore.com/public/1b019a4f65ab0694/node-canvas-font-mem-leak.zip

I suspect the problem is inside the `deregisterAllFonts()` function, which I myself wrote and introduced in [PR #1811](https://github.com/Automattic/node-canvas/pull/1811). However, I have been over the code many times and I can't see where anything could be leaking.

Another note of interest: The leak on macOS is easily 10 times worse than Linux, with the final memory clocking in at 4 GB after 2,500 iterations. It is also **much** slower, taking almost 10 minutes to run on my 2021 MBP. My theory here is that every time you call `deregisterAllFonts()` and then `registerFont()`, it re-registers **all** system fonts, and on macOS the list of built-in system fonts is vast. On my headless Linux box there are very few (if any) built-in system fonts, so the leak is slower there.

Final note: You don't actually have to call `fillText()` to reproduce the memory leak. Simply creating a canvas along with `registerFont()` and `deregisterAllFonts()` is enough to do it.

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.