How to (not) destroy() a widget?

GTK4 removed destroy() for non-toplevels in favor of regular ref counting. I can see how that keeps us honest on the C side, but it’s a bit of a hassle in high-level bindings like javascript.

Right now I’m addressing this along the lines of

    this.remove(child);
    child.run_dispose();

I do feel a bit dirty every time I type run_dispose() though, so I wonder if anyone has come up with a better pattern in JS?

Somewhat related, custom widget should call gtk_widget_unparent() on all their children, with the dispose vfunc as the canonical place to put the code.

That vfunc cannot be used from JS (the object is “too far gone” at that point), so is there a good solution for this?

The only thing I found that somewhat works is

function unparentRecursively(w) {
    [...w].forEach(
        child => unparentRecursively(child));
    w.unparent();
}
unparentRecursively(window)

There surely must be something less awkward?

4 Likes

Indeed, there is not a great solution for this as far as I know.

Calling run_dispose() is exactly the same as calling destroy(): the fact that it looks odd is intentional. Explicitly destroying widgets should be odd; either widgets should be removed from their parent via their parent’s API. If this is an implementation of a container, then having to call run_dispose() is really a GJS issue.

The fact that you can’t rely on dispose() is also quite unfortunate, but you have to remember that the destroy signal emission is also called inside the dispose() cycle; you were just relying on poorly defined side effects of run_dispose() causing a destroy(). Plus, this only ever worked on widgets, but there’s plenty of objects that take ownership of other objects.

widgets should be removed from their parent via their parent’s API.

The problem is that in JS (unlike C), this is rarely enough. If the widget is used in a closure, then that’ll keep it alive. At least until now, the canonical place for disconnecting signals is from a ::destroy handler, but well … we have a clear chicken and egg situation there.

Maybe moving signal cleanup to vfunc_unroot() works better (for widgets that aren’t meant to be reused), dunno. Looking for less awkward options is the point of the post.

Plus, this only ever worked on widgets, but there’s plenty of objects that take ownership of other objects.

Yes, but those are often tied to a widget lifecycle. That is, you disconnect any signals on destroy, set the property that stored the object to null, and let garbage collection do the cleanup.

You definitely want to drop references that keep alive the widget, like timeouts/idle sources, or tick callbacks, inside vfunc_unmap; closures that deal with hierarchy should, indeed, be dropped inside vfunc_unroot.

I mean, that’s just because how things have worked in the past when you had a signal that let you drop references. :slight_smile: It was a convenient place to hang ancillary objects because you had this weak reference mechanism.

In the Rust bindings, for instance, you can declare all the captured variables in a closure to be weak references; this way, the objects you reference in the closure will not acquire a hard reference, and will just exist during the closure itself.

In practice, I think GJS should expose weak references a lot more, considering that you have two garbage collection mechanisms—a delayed GC and a reference counting GC.

Alternatively, we’ll have to come up with more hooks into GObject.

Which also didn’t work for anything but GTK widgets. Because of the lack of proper weak references, many Python/GJS applications using e.g. GStreamer create reference cycles that are impossible to clean up automatically right now, which is unexpected behaviour at least.

That bindings were making use of this GTK-specific API to clean up reference cycles instead of a more general solution is rather suboptimal and caused quite a few memory leaks in applications.

The clone macro is pretty useful in that way for a parent widget connecting signals to its children but in other cases I’ve found it to be inadequate, some signal handlers really need to be manually disconnected on object destruction.

The way I’ve found around this for property handlers is to make heavy use of GBinding and GtkExpressionWatch which are able to manage the weak references internally, maybe there could be something similar for signals? I’ve noticed g_signal_connect_object isn’t usually exposed to language bindings, maybe it needs a version that takes a weak reference?

1 Like

In the next JS engine upgrade we’ll get WeakRef. I haven’t tested if it works for this purpose though! It’s not exactly a great developer experience though. Most documentation of the feature emphasizes that WeakRef should not be used by most JS developers.

I wonder if we could make this our official guidance?

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