Memory leak in GJS code

I’m working on a gnome extension and identified a memory leak. It is somewhere in the following code, which is called repeatedly in my extension in order to automatically update the color of some UI component:

updateColor() {
    const shooter = new Shell.Screenshot();
    const [content]: [Clutter.TextureContent] = await shooter.screenshot_stage_to_content();
    const wholeScreenTexture = content.get_texture();

    const area = {
        x: this.pill.x - 20 * this.scaleFactor,
        y: this.y,
        w: this.pill.width + 40 * this.scaleFactor,
        h: this.height,
    };

    const stream = Gio.MemoryOutputStream.new_resizable();
    const pixbuf: GdkPixbuf.Pixbuf = await Shell.Screenshot.composite_to_stream(  // takes around 4-14ms, most of the time 7ms
        wholeScreenTexture, area.x, area.y, area.w, area.h,
        this.scaleFactor, null, 0, 0, 1, stream
    );
    stream.close(null);
    // stream.run_dispose();  // tried this, didn't fix the memory leak

    const pixels = pixbuf.get_pixels();

    // [...] some pure js code iterating through `pixels`, with no references
    // to anything being kept after the function is done 
}

This function is at the moment called at a 500ms interval and my memory (16gb) slowly is eaten up, until it’s full after maybe 5-10 minutes. If I don’t run this code, the memory leak does not occurr at all, I’ve tried this a few times.

  1. Is there anything I’m doing wrong here, or any way to easily fix this?

  2. If not, is there any better way to repeatedly get all pixels in a certain area in GJS? I feel like this approach with Shell.Screenshot.composite_to_stream is somewhat ineffective since it always PNG-encodes the image data, but I was unable to find a better way (for context: the goal of the function is to calculate the average luminance of an area).

It’s possible you’ve found a memory leak. You can try running with Valgrind using the very handy toolbox scripts.

What’s more likely is you’re encountering an issue language bindings have with some objects like GdkPixbuf. As I understand it, there is no good mechanism for objects to express how much memory they’re using, so the garbage collector doesn’t recognize or prioritize them for collection.

GObject.Object.run_dispose() is occasionally useful in such cases, but not this one.

Using the screenshot method is a smart way to re-use existing code, but you might be better off doing this with Cairo directly (we have a special method for disposing Cairo Contexts to avoid memory balloons) or perhaps by emulating the internals of Shell.Screenshot.composite_to_stream().

Hopefully that helps a bit, I don’t have much experience manipulating surfaces and textures.

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.