Brooooooklyn / Brooooooklyn/Image

[Bug] Memory leaking in Transformer

Open
#128 1 comment 0 reactions 0 assignees View on GitHub
bug
Dominant language
Rust
Stars
416
Forks
13
Avg merge
10d 23h
Merged PRs (30d)
2

Description

### Search before asking

- [x] I searched in the [issues](https://github.com/Brooooooklyn/Image/issues) and found nothing similar.

### Image version

"@napi-rs/image": "^1.4.0"

### System version

macOS 15.4.1

### Node.js version

v23.11.0

### Minimal reproduce step

```js
import { JsColorType, Transformer } from '@napi-rs/image';
import { readFileSync } from 'fs';
async function runTest() {
const iterations = 10;
const file = readFileSync('/Users/jack/Downloads/image.png');
const formatMemoryUsage = (memoryUsage) => {
return {
rss: `${Math.round((memoryUsage.rss / 1024 / 1024) * 100) / 100} MB`,
heapTotal: `${Math.round((memoryUsage.heapTotal / 1024 / 1024) * 100) / 100} MB`,
heapUsed: `${Math.round((memoryUsage.heapUsed / 1024 / 1024) * 100) / 100} MB`,
external: `${Math.round((memoryUsage.external / 1024 / 1024) * 100) / 100} MB`,
};
};
console.log('Memory usage before tests:');
console.table(formatMemoryUsage(process.memoryUsage()));
for (let i = 0; i < iterations; i++) {
let transformer = new Transformer(Buffer.from(new Uint8Array(file)));
let { width, height, colorType } = await transformer.metadata();
if (colorType !== JsColorType.Rgba8 && colorType !== JsColorType.Rgb8) {
transformer = new Transformer(await transformer.png());
({ width, height, colorType } = await transformer.metadata());
}
if (colorType !== JsColorType.Rgba8 && colorType !== JsColorType.Rgb8) {
throw new TypeError('Cannot convert the given image to rgba8 format.');
}
let rgb = new Uint8ClampedArray(await transformer.rawPixels());
if (colorType === JsColorType.Rgb8)
rgb = rgb_to_rgba(rgb);
await Transformer.fromRgbaPixels(rgb, width, height).png();
// ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// memory leak here
gc?.({ execution: 'sync', type: 'major' });
if (i % 10 === 0 || i === iterations - 1) {
console.log(`[Iteration ${i}]`);
console.table(formatMemoryUsage(process.memoryUsage()));
}
}
console.log('Memory usage after all tests:');
console.table(formatMemoryUsage(process.memoryUsage()));
await new Promise((resolve) => setTimeout(resolve, 500));
}
function rgb_to_rgba(array) {
const next = new Uint8ClampedArray((array.length / 3) * 4);
for (var old_index = 0, new_index = 0; old_index < array.length; old_index += 3, new_index += 4) {
next[new_index] = array[old_index];
next[new_index + 1] = array[old_index + 1];
next[new_index + 2] = array[old_index + 2];
next[new_index + 3] = 255;
}
if (new_index !== next.length)
throw new Error('rgb_to_rgba error');
return next;
}
runTest().then(() => {
gc?.({ execution: 'sync', type: 'major' });
debugger;
});
```

### What did you expect to see?

No memory leaking

### What did you see instead?

Image

Each iteration keeps 16.8 MB cannot be released.

### Anything else?

initially discovered by @jhumain in https://github.com/DimensionDev/Stego-JS/issues/52

### Are you willing to submit a PR?

- [ ] I'm willing to submit a PR!

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with the provided Node.js reproduction and the Transformer.fromRgbaPixels call highlighted as leaking memory. Trace the native allocation and lifetime used by that entry point, then rerun the loop with forced garbage collection. Done means repeated iterations no longer retain the reported 16.8 MB per iteration.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, node.js, rust
Domain
computer-graphics
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.