Skip to content

[Bug] Memory leaking in Transformer #128

@Jack-Works

Description

@Jack-Works

Search before asking

  • I searched in the 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

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 DimensionDev/Stego-JS#52

Are you willing to submit a PR?

  • I'm willing to submit a PR!

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions