gtkgjs

Understanding gjs memory managment warning


I'm new to gjs and gtk, so this may be very basic, but I did not find any other question regarding this.

While trying to understand the gjs bindings of gtk and gdk, I wanted to write a small script that would log the dimensions of the monitor where a window would open, so I came up with the following one:

import Gtk from 'gi://Gtk?version=4.0'
import Gdk from 'gi://Gdk?version=4.0'

// Gtk needs to be initialized to get a non-null default display, I think
Gtk.init()

const {Display, Surface} = Gdk
const display = Display.get_default()
const surface = Surface.new_toplevel(display)

const {geometry: {width, height}} = display.get_monitor_at_surface(surface)
console.log(`Width:  ${width}`)
console.log(`Height: ${height}`)
surface.destroy()

While this script exits successfully (status code 0), it logs something like the following line:

(gjs:12345): Gjs-CRITICAL **: 00:00:00.000: Object 0x112233445566 of type GdkWaylandToplevel has been finalized while it was still owned by gjs, this is due to invalid memory management.

However, the following experiments left me confused as to how to appropriately modify this script:

What would be the appropriate way to manage the memory in this script?

Edit:

While integrating the aquired dimensions to run a nested wayland session with the appropriate size, I realized that the critical message is logged when the script finished on its own but not when it is aborted via Ctrl+C. A minimal working example that does not log any irrelevant messages is:

import Gtk from 'gi://Gtk?version=4.0'
import Gdk from 'gi://Gdk?version=4.0'
import Gio from 'gi://Gio'

Gtk.init()

const {Display, Surface} = Gdk
const {Subprocess, SubprocessFlags: {SEARCH_PATH_FROM_ENVP}} = Gio

Gio._promisify(Subprocess.prototype, 'wait_async', 'wait_finish')

const display = Display.get_default()
const surface = Surface.new_toplevel(display)
const {geometry: {width, height}} = display.get_monitor_at_surface(surface)
surface.destroy()

await Subprocess.new(
  ['sleep', '5'],
  SEARCH_PATH_FROM_ENVP
).wait_async(null)

As explained a bit earlier, if we wait 5 secends after running this script, the script finishes on its own and logs the critical message; however, if we press Ctrl+C before that, it aborts and that message is never logged.


Solution

  • This workaround relies on a bug that is already patched and will be released in GNOME 46; please, read the update.

    Executing surface.ref() before destroying the surface got rid of the message.

    import Gtk from 'gi://Gtk?version=4.0'
    import Gdk from 'gi://Gdk'
    
    // Gtk needs to be initialized to get a non-null default display, I think
    Gtk.init()
    
    const {Display, Surface} = Gdk
    const display = Display.get_default()
    const surface = Surface.new_toplevel(display)
    
    const {geometry: {width, height}} = display.get_monitor_at_surface(surface)
    console.log(`Width:  ${width}`)
    console.log(`Height: ${height}`)
    surface.ref()
    surface.destroy()
    

    I still don't understand why, but as a suggestion from @andy.holmes, I opened an issue on the gjs project. I'll update this answer with the relevant feedback I get there.

    Updates:

    The general rule of thumb is not to destroy objects unless there is a reason to do so documented in the official documentation since reference counting should take care of releasing resources in most cases. Moreover, if for some reason, the destroy method is used or expected to be used somewhere, all holders of this reference should listen for its destroy signal and release such reference, which is an unneeded complication most of the time, as explained by @andy.holmes here.

    The behaviour described earlier was due to a bug addressed in issue 592 of gjs, where an unnecessary warning was issued if the Gdk.Surface lost all references without been destroyed. Further information regarding the bug can be found there.