Closed rambo-panda closed 1 week ago
After several tests on my side and after proceeding by elimination, I confirm I have the same issue.
The buffers generated by drawImage
cannot be cleared by Node's Garbage Collector, inevitably leading to a memory leak.
I have the same issue
// 'static pointer
let self_mut = unsafe { Box::leak(Box::from_raw(image_ptr.cast::<Image>())) };
// 'static pointer let self_mut = unsafe { Box::leak(Box::from_raw(image_ptr.cast::<Image>())) };
@Brooooooklyn since I have limited understanding of @napi-rs
, I am hesitant to change the lifecycle of self_mut
rashly. Is it possible to make the following modification for the time being?
#[napi(setter)]
pub fn set_src(&mut self, env: Env, this: This, data: Uint8Array) -> Result<()> {
let length = data.len();
if length <= 2 {
self.src = Some(data);
+ self.bitmap = None;
+ self.width = -1.0;
+ self.height = -1.0;
Of course, I have my own motives, as it just happens to meet another need of mine. https://github.com/Brooooooklyn/canvas/issues/868
@Brooooooklyn Hello, excuse me for asking, but what version of rustc
are you using for the compilation? I've been using a Docker container created on my home computer, and I find it unable to compile properly.
npm run build
error log
warning: use of deprecated struct `napi::JsBuffer`: Please use Buffer or &[u8] instead
--> src/svg.rs:11:13
|
11 | ) -> Result<JsBuffer> {
| ^^^^^^^^
error[E0308]: `?` operator has incompatible types
--> src/image.rs:242:17
|
242 | this_ref: env.create_reference(&this)?,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `Ref<()>`, found `Ref<JsObject>`
|
= note: `?` operator cannot convert from `napi::Ref<JsObject>` to `napi::Ref<()>`
= note: expected struct `napi::Ref<()>`
found struct `napi::Ref<JsObject>`
error[E0277]: the trait bound `(): napi::NapiValue` is not satisfied
--> src/image.rs:426:46
|
426 | let this: This = env.get_reference_value(&self.this_ref)?;
| ------------------- ^^^^^^^^^^^^^^ the trait `napi::NapiValue` is not implemented for `()`
| |
| required by a bound introduced by this call
|
= help: the following other types implement trait `napi::NapiValue`:
JsArrayBuffer
JsBoolean
JsDataView
JsExternal
JsFunction
JsGlobal
JsNull
JsNumber
and 9 others
note: required by a bound in `napi::Env::get_reference_value`
--> /root/.cargo/registry/src/index.crates.io-6f17d22bba15001f/napi-3.0.0-alpha.9/src/env.rs:884:8
|
882 | pub fn get_reference_value<T>(&self, reference: &Ref<T>) -> Result<T>
| ------------------- required by a bound in this associated function
883 | where
884 | T: NapiValue,
| ^^^^^^^^^ required by this bound in `Env::get_reference_value`
error[E0308]: `?` operator has incompatible types
--> src/image.rs:426:22
|
426 | let this: This = env.get_reference_value(&self.this_ref)?;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `JsObject`, found `()`
|
= note: `?` operator cannot convert from `()` to `JsObject`
warning: unused import: `NapiRaw`
--> src/image.rs:5:46
|
5 | use napi::{bindgen_prelude::*, check_status, NapiRaw, NapiValue, Ref};
| ^^^^^^^
|
= note: `#[warn(unused_imports)]` on by default
Some errors have detailed explanations: E0277, E0308.
For more information about an error, try `rustc --explain E0277`.
warning: `canvas` (lib) generated 9 warnings
The following warnings were emitted during compilation:
warning: canvas@0.1.0: skia-c/skia_c.cpp:455:19: warning: variable length arrays in C++ are a Clang extension [-Wvla-cxx-extension]
warning: canvas@0.1.0: 455 | SkRect bounds[text_len];
warning: canvas@0.1.0: | ^~~~~~~~
warning: canvas@0.1.0: skia-c/skia_c.cpp:455:19: note: function parameter 'text_len' with unknown value cannot be used in a constant expression
warning: canvas@0.1.0: skia-c/skia_c.cpp:398:14: note: declared here
warning: canvas@0.1.0: 398 | size_t text_len,
warning: canvas@0.1.0: | ^
warning: canvas@0.1.0: 1 warning generated.
error: could not compile `canvas` (lib) due to 3 previous errors; 9 warnings emitted
@Brooooooklyn Hello, excuse me for asking, but what version of
rustc
are you using for the compilation? I've been using a Docker container created on my home computer, and I find it unable to compile properly.
npm run build
error log ...
https://github.com/Brooooooklyn/canvas/pull/898/commits/e3ce67e6be5f29b6e0da3d85af9cdeed7a15dc40
@Brooooooklyn Hi, are there any progress on this issue, it's a serious problem on server, after a few requests, the server memory usage is full.
@Brooooooklyn I tested and found that the memory usage has not decreased for a long time. This is my test code.
import { clearAllCache, createCanvas, loadImage } from "@napi-rs/canvas";
const imgBuf = await fetch(
"https://cdn-ms.17zuoye.cn/zx-ptqlm/testing_2023_10_27/pdf_6d1e8d355613ef824d_0.webp",
).then((a) => a.arrayBuffer());
const len = ~~(process.argv[2]??50);
console.time("drawImage");
const res = await Promise.all(
Array(len)
.fill(0)
.map(async () => {
// console.time(i);
const img = await loadImage(imgBuf);
const canvas = createCanvas(img.width, img.height);
canvas.getContext("2d").drawImage(img, 0, 0, img.width, img.height);
await canvas.encode("webp", 80);
// console.timeEnd(i);
}),
);
console.timeEnd("drawImage");
setInterval(() => {
global.gc?.();
clearAllCache();
console.log(process.memoryUsage.rss() / 1024 / 1024);
// decreasing array length
res.splice(0, 10);
}, 1000);
If I reuse the script from @rambo-panda , the fix doesn't seem to work
If I reuse the script from @rambo-panda , the fix doesn't seem to work
Why do you think it doesn't work? The memoryUsage.rss()
can not be 0
If I reuse the script from @rambo-panda , the fix doesn't seem to work
Why do you think it doesn't work? The
memoryUsage.rss()
can not be 0.
Because in my script I generate GIFs (around 5 each time) Each frame has about 10 to 20 loadImage() calls, and each GIF has between 5 and 10 frames When I run this script three times, my 8GB of RAM gets maxed out, and Ubuntu starts using swap, if the server doesn't crash
However, the script is written in TypeScript, so the promises are properly resolved I reused Panda's script to better illustrate the issue
But in Chrome's inspector, when using the --inspect
flag, you can clearly see that ArrayBuffer
instances keep growing and are never purged
The only way to clean them up is to stop the script or wait for the server to crash
Alternatively with PM2, I can set a memory usage limit so that PM2 automatically restarts the process
I had also modified Panda's script by logging the RSS by default before logging it at intervals
At the beginning, it reported 61-62MB of usage, which is normal
But after that, it stayed stuck at 90MB
Normally, after clearing the constant named res
that holds all the loadImage
instances, it should have gone back down to around 65MB, or maybe 70MB if some compiled code was added to the RSS during execution
However, that's not the case
The amount of RAM used by the ArrayBuffer
generated by loadImage
increases proportionally and exponentially each time the script is called
I tested 0.1.58
on my server, the same result.
The memory did not released until my server got a maxMemoryUsage
crash
@bingtsingw can't reproduce the memory leak with this:
import { whiteBright, red, green, gray } from 'colorette'
import prettyBytes from 'pretty-bytes'
import { table } from 'table'
import { clearAllCache, createCanvas, loadImage } from "./index.js";
const imgBuf = await fetch(
"https://cdn-ms.17zuoye.cn/zx-ptqlm/testing_2023_10_27/pdf_6d1e8d355613ef824d_0.webp",
).then((a) => a.arrayBuffer());
const initialMemoryUsage = process.memoryUsage()
const len = ~~(process.argv[2] ?? 50);
console.time("drawImage");
const res = await Promise.all(
Array(len)
.fill(0)
.map(async () => {
// console.time(i);
const img = await loadImage(imgBuf);
const canvas = createCanvas(img.width, img.height);
canvas.getContext("2d").drawImage(img, 0, 0, img.width, img.height);
await canvas.encode("webp", 80);
// console.timeEnd(i);
}),
);
console.timeEnd("drawImage");
function displayMemoryUsageFromNode(initialMemoryUsage) {
const finalMemoryUsage = process.memoryUsage()
const titles = Object.keys(initialMemoryUsage).map((k) => whiteBright(k))
const tableData = [titles]
const diffColumn = []
for (const [key, value] of Object.entries(initialMemoryUsage)) {
const diff = finalMemoryUsage[key] - value
const prettyDiff = prettyBytes(diff, { signed: true })
if (diff > 0) {
diffColumn.push(red(prettyDiff))
} else if (diff < 0) {
diffColumn.push(green(prettyDiff))
} else {
diffColumn.push(gray(prettyDiff))
}
}
tableData.push(diffColumn)
console.info(table(tableData))
}
setInterval(() => {
global.gc?.();
clearAllCache();
displayMemoryUsageFromNode(initialMemoryUsage);
// decreasing array length
res.splice(0, 10);
}, 1000);
And the output:
╔══════════╤═══════════╤══════════╤══════════╤══════════════╗
║ rss │ heapTotal │ heapUsed │ external │ arrayBuffers ║
╟──────────┼───────────┼──────────┼──────────┼──────────────╢
║ +53.3 MB │ 0 B │ +150 kB │ +33.5 kB │ 0 B ║
╚══════════╧═══════════╧══════════╧══════════╧══════════════╝
╔══════════╤═══════════╤══════════╤══════════╤══════════════╗
║ rss │ heapTotal │ heapUsed │ external │ arrayBuffers ║
╟──────────┼───────────┼──────────┼──────────┼──────────────╢
║ +54.3 MB │ +524 kB │ -347 kB │ +33.5 kB │ -492 kB ║
╚══════════╧═══════════╧══════════╧══════════╧══════════════╝
╔══════════╤═══════════╤══════════╤══════════╤══════════════╗
║ rss │ heapTotal │ heapUsed │ external │ arrayBuffers ║
╟──────────┼───────────┼──────────┼──────────┼──────────────╢
║ +54.7 MB │ +524 kB │ +319 kB │ +33.5 kB │ -492 kB ║
╚══════════╧═══════════╧══════════╧══════════╧══════════════╝
╔════════╤═══════════╤══════════╤══════════╤══════════════╗
║ rss │ heapTotal │ heapUsed │ external │ arrayBuffers ║
╟────────┼───────────┼──────────┼──────────┼──────────────╢
║ +56 MB │ +1.05 MB │ -576 kB │ -492 kB │ -492 kB ║
╚════════╧═══════════╧══════════╧══════════╧══════════════╝
╔══════════╤═══════════╤══════════╤══════════╤══════════════╗
║ rss │ heapTotal │ heapUsed │ external │ arrayBuffers ║
╟──────────┼───────────┼──────────┼──────────┼──────────────╢
║ +56.3 MB │ +1.31 MB │ +111 kB │ -492 kB │ -492 kB ║
╚══════════╧═══════════╧══════════╧══════════╧══════════════╝
╔══════════╤═══════════╤══════════╤══════════╤══════════════╗
║ rss │ heapTotal │ heapUsed │ external │ arrayBuffers ║
╟──────────┼───────────┼──────────┼──────────┼──────────────╢
║ +56.3 MB │ +1.31 MB │ +459 kB │ -492 kB │ -492 kB ║
╚══════════╧═══════════╧══════════╧══════════╧══════════════╝
╔══════════╤═══════════╤══════════╤══════════╤══════════════╗
║ rss │ heapTotal │ heapUsed │ external │ arrayBuffers ║
╟──────────┼───────────┼──────────┼──────────┼──────────────╢
║ +56.4 MB │ +1.31 MB │ +810 kB │ -492 kB │ -492 kB ║
╚══════════╧═══════════╧══════════╧══════════╧══════════════╝
╔══════════╤═══════════╤══════════╤══════════╤══════════════╗
║ rss │ heapTotal │ heapUsed │ external │ arrayBuffers ║
╟──────────┼───────────┼──────────┼──────────┼──────────────╢
║ +56.4 MB │ +1.31 MB │ +1.16 MB │ -492 kB │ -492 kB ║
╚══════════╧═══════════╧══════════╧══════════╧══════════════╝
╔══════════╤═══════════╤══════════╤══════════╤══════════════╗
║ rss │ heapTotal │ heapUsed │ external │ arrayBuffers ║
╟──────────┼───────────┼──────────┼──────────┼──────────────╢
║ +51.3 MB │ -3.15 MB │ -1.83 MB │ -500 kB │ -497 kB ║
╚══════════╧═══════════╧══════════╧══════════╧══════════════╝
╔══════════╤═══════════╤══════════╤══════════╤══════════════╗
║ rss │ heapTotal │ heapUsed │ external │ arrayBuffers ║
╟──────────┼───────────┼──────────┼──────────┼──────────────╢
║ +51.4 MB │ -3.15 MB │ -1.69 MB │ -500 kB │ -497 kB ║
╚══════════╧═══════════╧══════════╧══════════╧══════════════╝
╔══════════╤═══════════╤══════════╤══════════╤══════════════╗
║ rss │ heapTotal │ heapUsed │ external │ arrayBuffers ║
╟──────────┼───────────┼──────────┼──────────┼──────────────╢
║ +51.8 MB │ -3.15 MB │ -1.13 MB │ -500 kB │ -497 kB ║
╚══════════╧═══════════╧══════════╧══════════╧══════════════╝
╔══════════╤═══════════╤══════════╤══════════╤══════════════╗
║ rss │ heapTotal │ heapUsed │ external │ arrayBuffers ║
╟──────────┼───────────┼──────────┼──────────┼──────────────╢
║ +52.2 MB │ -3.15 MB │ -772 kB │ -500 kB │ -497 kB ║
╚══════════╧═══════════╧══════════╧══════════╧══════════════╝
╔══════════╤═══════════╤══════════╤══════════╤══════════════╗
║ rss │ heapTotal │ heapUsed │ external │ arrayBuffers ║
╟──────────┼───────────┼──────────┼──────────┼──────────────╢
║ +52.6 MB │ -2.1 MB │ -1.41 MB │ -500 kB │ -497 kB ║
╚══════════╧═══════════╧══════════╧══════════╧══════════════╝
╔════════╤═══════════╤══════════╤══════════╤══════════════╗
║ rss │ heapTotal │ heapUsed │ external │ arrayBuffers ║
╟────────┼───────────┼──────────┼──────────┼──────────────╢
║ +53 MB │ -2.1 MB │ -1.06 MB │ -500 kB │ -497 kB ║
╚════════╧═══════════╧══════════╧══════════╧══════════════╝
╔══════════╤═══════════╤══════════╤══════════╤══════════════╗
║ rss │ heapTotal │ heapUsed │ external │ arrayBuffers ║
╟──────────┼───────────┼──────────┼──────────┼──────────────╢
║ +53.3 MB │ -2.1 MB │ -713 kB │ -500 kB │ -497 kB ║
╚══════════╧═══════════╧══════════╧══════════╧══════════════╝
╔══════════╤═══════════╤══════════╤══════════╤══════════════╗
║ rss │ heapTotal │ heapUsed │ external │ arrayBuffers ║
╟──────────┼───────────┼──────────┼──────────┼──────────────╢
║ +52.5 MB │ -3.15 MB │ -1.35 MB │ -500 kB │ -497 kB ║
╚══════════╧═══════════╧══════════╧══════════╧══════════════╝
╔══════════╤═══════════╤══════════╤══════════╤══════════════╗
║ rss │ heapTotal │ heapUsed │ external │ arrayBuffers ║
╟──────────┼───────────┼──────────┼──────────┼──────────────╢
║ +52.5 MB │ -3.15 MB │ -962 kB │ -500 kB │ -497 kB ║
╚══════════╧═══════════╧══════════╧══════════╧══════════════╝
╔══════════╤═══════════╤══════════╤══════════╤══════════════╗
║ rss │ heapTotal │ heapUsed │ external │ arrayBuffers ║
╟──────────┼───────────┼──────────┼──────────┼──────────────╢
║ +52.5 MB │ -3.15 MB │ -632 kB │ -500 kB │ -497 kB ║
╚══════════╧═══════════╧══════════╧══════════╧══════════════╝
╔══════════╤═══════════╤══════════╤══════════╤══════════════╗
║ rss │ heapTotal │ heapUsed │ external │ arrayBuffers ║
╟──────────┼───────────┼──────────┼──────────┼──────────────╢
║ +51.7 MB │ -3.15 MB │ -1.29 MB │ -500 kB │ -497 kB ║
╚══════════╧═══════════╧══════════╧══════════╧══════════════╝
╔════════╤═══════════╤══════════╤══════════╤══════════════╗
║ rss │ heapTotal │ heapUsed │ external │ arrayBuffers ║
╟────────┼───────────┼──────────┼──────────┼──────────────╢
║ +52 MB │ -3.15 MB │ -958 kB │ -500 kB │ -497 kB ║
╚════════╧═══════════╧══════════╧══════════╧══════════════╝
╔══════════╤═══════════╤══════════╤══════════╤══════════════╗
║ rss │ heapTotal │ heapUsed │ external │ arrayBuffers ║
╟──────────┼───────────┼──────────┼──────────┼──────────────╢
║ +51.5 MB │ -3.15 MB │ -1.46 MB │ -500 kB │ -497 kB ║
╚══════════╧═══════════╧══════════╧══════════╧══════════════╝
╔══════════╤═══════════╤══════════╤══════════╤══════════════╗
║ rss │ heapTotal │ heapUsed │ external │ arrayBuffers ║
╟──────────┼───────────┼──────────┼──────────┼──────────────╢
║ +51.9 MB │ -3.15 MB │ -1.13 MB │ -500 kB │ -497 kB ║
╚══════════╧═══════════╧══════════╧══════════╧══════════════╝
@Brooooooklyn Thank you for you test script, I'll use this method to test my image.
I have the same issue, noticed it while periodically checking process.memoryUsage().rss
. I have tried the test script provided by @Brooooooklyn and can reproduce the issue. I used a different image, a webp image which is 640.016 bytes and has a resolution of 3840 x 2160px. I used const imgBuf = await readFile("test.webp");
to load the image buffer.
when I run it in a container on Ubuntu 22 LTS, there is a severe memory leak.
-import { clearAllCache, createCanvas, loadImage } from "./index.js";
+import { clearAllCache, createCanvas, loadImage } from '@napi-rs/canvas';
+
const imgBuf = await fetch(
"https://cdn-ms.17zuoye.cn/zx-ptqlm/testing_2023_10_27/pdf_6d1e8d355613ef824d_0.webp",
-).then((a) => a.arrayBuffer());
+).then((a) => a.arrayBuffer()).then(Buffer.from);
const initialMemoryUsage = process.memoryUsage()
@@ -45,6 +46,7 @@ function displayMemoryUsageFromNode(initialMemoryUsage) {
}
tableData.push(diffColumn)
console.info(table(tableData))
+ console.info("current RSS value", process.memoryUsage.rss());
}
after 1min
current RSS value 4704.67578125Mb
╔══════════╤═══════════╤══════════╤══════════╤══════════════╗
║ rss │ heapTotal │ heapUsed │ external │ arrayBuffers ║
╟──────────┼───────────┼──────────┼──────────┼──────────────╢
║ +4.87 GB │ -3.15 MB │ -1.69 MB │ +2.57 MB │ -497 kB ║
╚══════════╧═══════════╧══════════╧══════════╧══════════════╝
current RSS value 4704.93359375Mb
╔══════════╤═══════════╤══════════╤══════════╤══════════════╗
║ rss │ heapTotal │ heapUsed │ external │ arrayBuffers ║
╟──────────┼───────────┼──────────┼──────────┼──────────────╢
║ +4.87 GB │ -3.15 MB │ -1.35 MB │ +2.57 MB │ -497 kB ║
╚══════════╧═══════════╧══════════╧══════════╧══════════════╝
current RSS value 4705.19140625Mb
╔══════════╤═══════════╤══════════╤══════════╤══════════════╗
║ rss │ heapTotal │ heapUsed │ external │ arrayBuffers ║
╟──────────┼───────────┼──────────┼──────────┼──────────────╢
║ +4.87 GB │ -3.15 MB │ -1.02 MB │ +2.57 MB │ -497 kB ║
╚══════════╧═══════════╧══════════╧══════════╧══════════════╝
current RSS value 4704.70703125Mb
╔══════════╤═══════════╤══════════╤══════════╤══════════════╗
║ rss │ heapTotal │ heapUsed │ external │ arrayBuffers ║
╟──────────┼───────────┼──────────┼──────────┼──────────────╢
║ +4.87 GB │ -3.15 MB │ -1.7 MB │ +2.57 MB │ -497 kB ║
╚══════════╧═══════════╧══════════╧══════════╧══════════════╝
current RSS value 4704.96484375Mb
@rambo-panda your memory leak was caused by then((a) => a.arrayBuffer()).then(Buffer.from)
, not from @napi-rs/canvas
@rambo-panda your memory leak was caused by
then((a) => a.arrayBuffer()).then(Buffer.from)
, not from@napi-rs/canvas
@Brooooooklyn sorry, I didn't understand. Even if I try to download the image locally and then use fs.readFileSync
to read it before performing the loadImage
operation, the RSS still remains high for a long time.
@rambo-panda your memory leak was caused by
then((a) => a.arrayBuffer()).then(Buffer.from)
, not from@napi-rs/canvas
@Brooooooklyn This script is what I used to test it, I still get a severe memory leak with this, in my comment earlier I loaded a different image via fs.readFile()
, now I ran this code and the issue is the same.
import { whiteBright, red, green, gray } from 'colorette';
import prettyBytes from 'pretty-bytes';
import { table } from 'table';
import { clearAllCache, createCanvas, loadImage } from "@napi-rs/canvas";
const imgBuf = await fetch(
"https://cdn-ms.17zuoye.cn/zx-ptqlm/testing_2023_10_27/pdf_6d1e8d355613ef824d_0.webp",
).then((a) => a.arrayBuffer());
// Tried this too, same result
// import { readFile } from 'node:fs/promises';
// const imgBuf = await readFile("pdf_6d1e8d355613ef824d_0.webp");
const initialMemoryUsage = process.memoryUsage();
const len = ~~(process.argv[2] ?? 50);
console.time("drawImage");
const res = await Promise.all(
Array(len)
.fill(0)
.map(async () => {
// console.time(i);
const img = await loadImage(imgBuf);
const canvas = createCanvas(img.width, img.height);
canvas.getContext("2d").drawImage(img, 0, 0, img.width, img.height);
await canvas.encode("webp", 80);
// console.timeEnd(i);
}),
);
console.timeEnd("drawImage");
function displayMemoryUsageFromNode(initialMemoryUsage) {
const finalMemoryUsage = process.memoryUsage();
const titles = Object.keys(initialMemoryUsage).map((k) => whiteBright(k));
const tableData = [titles];
const diffColumn = [];
for (const [key, value] of Object.entries(initialMemoryUsage)) {
const diff = finalMemoryUsage[key] - value;
const prettyDiff = prettyBytes(diff, { signed: true });
if (diff > 0) {
diffColumn.push(red(prettyDiff));
} else if (diff < 0) {
diffColumn.push(green(prettyDiff));
} else {
diffColumn.push(gray(prettyDiff));
}
}
tableData.push(diffColumn);
console.info(table(tableData));
}
setInterval(() => {
global.gc?.();
clearAllCache();
displayMemoryUsageFromNode(initialMemoryUsage);
// decreasing array length
res.splice(0, 10);
}, 1000);
I let the code run for a while, nothing really changed.
same here, memory leaks still exist on 0.1.59
Can you test again with 0.1.60
? https://github.com/Brooooooklyn/canvas/releases/tag/v0.1.60
@Brooooooklyn Looks good. My initial tests seem to confirm that the leak is fixed in 0.1.60
. I've used the same script to test and I can't find any memory at the moment.
@Brooooooklyn amazing!! the 0.1.60
significantly alleviated memory issues.
However, when I tested with a sample size of 50, I found that although the memory usage decreased significantly compared to version 0.1.59
, it still exhibits noticeable slowdowns and some memory is not released.
From the above image, it can be seen that 2GB of memory has been reclaimed, but there is still 2GB that has not been reclaimed for a long time.
However, when I start expose-gc
, the memory recovery is particularly noticeable.
there is my envinfo
System:
OS: Linux 3.10 Ubuntu 22.04.1 LTS 22.04.1 LTS (Jammy Jellyfish)
CPU: (16) x64 Intel Xeon Processor (Cascadelake)
Memory: 22.42 GB / 31.42 GB
Container: Yes
Shell: 5.1.16 - /bin/bash
Binaries:
Node: 20.14.0 - /usr/bin/node
npm: 10.7.0 - /usr/bin/npm
It seems that the memory leak issue has been resolved. Regarding the issue https://github.com/Brooooooklyn/canvas/issues/890#issuecomment-2466625653 mentioned in the comment, I noticed @Brooooooklyn used the napi_adjust_external_memory API.
Here is my understanding of why the remaining 2GB has not been released.(I have not studied NAPI in depth, so if there are any misunderstandings, I hope everyone will enlighten me.)
Using the example of var img = Image(src);
, when V8 recovers the JavaScript variable img
, it will trigger its destructor function and pass a series of callbacks to the raw_finalize_unchecked
method of napi-rs
. Here, Rust
will recover the Rust object from the raw pointer through data.cast
. Then, by following the lifecycle rules, it will recover its memory. Subsequently, napi_adjust_external_memory
is used to notify V8 that the amount of external memory has changed. It should be noted that this adjustment only informs V8 of the change in memory usage, but does not necessarily trigger an immediate garbage collection (reduction of this memory usage) by V8.
The memory does not automatically drop to normal levels, even though I have enabled GC and cleared all cache.