apprt/gtk: add WeakRef.deinit and use it at teardown sites

A GWeakRef must be released before the memory holding it is freed: the
target keeps a pointer to the GWeakRef so it can clear it at finalize,
and if that memory is gone by then the target walks into whatever now
occupies it. inspector_window.zig already carries this warning, and
every call site follows it — but the rule lives in a comment in one
file, while the type itself offers only set and get, so releasing one
looks like an ordinary assignment.

Give it a name. deinit forwards to g_weak_ref_clear, which is the call
GLib documents for a GWeakRef that is going away, and the dispose-time
clears now use it. set(null) still works and is unchanged; the clear in
handleReloadConfig stays a set(null) because the object is still alive
there and the reference is reused.

Zig has no destructors so this enforces nothing. It puts the
requirement on the type someone is already looking at.
This commit is contained in:
Håkon Hægland
2026-08-10 15:24:57 +02:00
parent ec58fbc6a2
commit 1dbc8ca30c
5 changed files with 21 additions and 4 deletions

View File

@@ -1548,7 +1548,7 @@ pub const Application = extern struct {
diag.close();
diag.unref(); // strong ref from get()
}
priv.config_errors_dialog.set(null);
priv.config_errors_dialog.deinit();
if (priv.signal_source) |v| {
if (glib.Source.remove(v) == 0) {
log.warn("unable to remove signal source", .{});

View File

@@ -609,7 +609,7 @@ const Command = extern struct {
switch (priv.data) {
.regular => {},
.jump => |*j| {
j.surface.set(null);
j.surface.deinit();
},
}

View File

@@ -647,7 +647,7 @@ pub const SplitTree = extern struct {
fn dispose(self: *Self) callconv(.c) void {
const priv = self.private();
priv.last_focused.set(null);
priv.last_focused.deinit();
if (priv.rebuild_source) |v| {
if (glib.Source.remove(v) == 0) {
log.warn("unable to remove rebuild source", .{});

View File

@@ -1296,7 +1296,7 @@ pub const Window = extern struct {
priv.handle_active_state_source = null;
}
priv.command_palette.set(null);
priv.command_palette.deinit();
if (priv.config) |v| {
v.unref();

View File

@@ -22,6 +22,23 @@ pub fn WeakRef(comptime T: type) type {
}
}
/// Release this weak reference.
///
/// You MUST call this before the memory holding this struct is freed,
/// which in practice means from the owner's `dispose`. The target keeps
/// a pointer to this `GWeakRef` so that it can clear it when the target
/// is finalized; if this memory is gone by then, the target walks into
/// whatever now occupies it. That is an invalid read at best, and can
/// hang: the target takes a lock inside each registered weak ref, and
/// reused memory with the low bit set is a lock nothing will release.
///
/// `set(null)` also unregisters and remains valid. This exists so the
/// requirement has a name at the use site rather than looking like an
/// ordinary assignment.
pub fn deinit(self: *Self) void {
self.ref.clear();
}
/// Get a strong reference to the object, or null if the object
/// has been finalized. This increases the reference count by one.
pub fn get(self: *Self) ?*T {