Brooooooklyn / Brooooooklyn/Image
[Bug] Memory leaking in Transformer
- 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?
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