fix: hold the check-button reference across the signal-panel rebuild
`gtk_check_button_set_active` emits both `notify::active` and `toggled` in sequence. GLib takes a per-emission reference, so it protects the instance only through the end of `notify::active` — but our `on_signal_switch` handler calls `rebuild_signal_panels`, which removes every row from `signal_list`. The row that contained the emitting check button is destroyed, dropping its reference. After `notify::active` finishes, GLib unrefs the instance: with no external holder left that is refcount 0, the instance is finalised. The subsequent `toggled` emission then fires into freed memory and produces two `GLib-GObject-CRITICAL` diagnostics. The fix: take a `g_object_ref` at the top of the handler and release it from a `g_idle_add_full` destroy function. The idle fires after `set_active` has returned and all its emissions are complete, so the instance lives until the emission chain is fully done. The rebuild itself stays synchronous — it has run before the next frame reads the list — and the behaviour the handler delivers is unchanged. This is a latent bug predating Task 1. Previously the window's focus reference happened to land on the row's `GtkListBoxRow` and kept the row alive as a side effect; the new `AdwViewSwitcher` takes initial focus instead, so nothing holds that reference any more. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
44d2fd35dc
commit
f224f56c4c
1 file changed
+14
@@ -24,6 +24,15 @@ void on_signal_switch(GObject* obj, GParamSpec*, gpointer user) {
|
|||||||
const std::vector<SignalInfo>& signals = app->model->signals();
|
const std::vector<SignalInfo>& signals = app->model->signals();
|
||||||
if (index < signals.size()) name = signals[index].name;
|
if (index < signals.size()) name = signals[index].name;
|
||||||
}
|
}
|
||||||
|
// Hold a reference on the emitting widget across the rebuild. `rebuild_signal_panels` destroys
|
||||||
|
// the list that owns `obj` (this check button's row), dropping the row's reference and leaving
|
||||||
|
// only GLib's own per-emission ref. `gtk_check_button_set_active` emits both `notify::active`
|
||||||
|
// and `toggled` in sequence: GLib's ref covers only one emission at a time, so after
|
||||||
|
// `notify::active` finishes GLib unrefs the instance and, without our extra hold, that is the
|
||||||
|
// last reference -- and the subsequent `toggled` emission is then into freed memory, which is
|
||||||
|
// the two `GLib-GObject-CRITICAL` diagnostics. Taking our own ref here and releasing it from
|
||||||
|
// an idle keeps the instance alive until after `set_active` has emitted everything and returned.
|
||||||
|
g_object_ref(obj);
|
||||||
app->model->set_signal_enabled(index, on);
|
app->model->set_signal_enabled(index, on);
|
||||||
if (!on) {
|
if (!on) {
|
||||||
// A trace of a signal nobody acquires draws nothing, and holds the slot a live one could take.
|
// A trace of a signal nobody acquires draws nothing, and holds the slot a live one could take.
|
||||||
@@ -48,6 +57,11 @@ void on_signal_switch(GObject* obj, GParamSpec*, gpointer user) {
|
|||||||
}
|
}
|
||||||
// The enable state is part of the session (a signal can be on only as a math channel's source).
|
// The enable state is part of the session (a signal can be on only as a math channel's source).
|
||||||
schedule_layout_save(*app);
|
schedule_layout_save(*app);
|
||||||
|
// Release the extra reference from an idle so that `set_active`'s full emission chain --
|
||||||
|
// `notify::active` and `toggled` -- has completed before the instance can be finalized.
|
||||||
|
g_idle_add_full(G_PRIORITY_DEFAULT_IDLE,
|
||||||
|
+[](gpointer) -> gboolean { return G_SOURCE_REMOVE; },
|
||||||
|
obj, g_object_unref);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The palette entry a signal owns: its position among the *plottable* signals, the
|
/// The palette entry a signal owns: its position among the *plottable* signals, the
|
||||||
|
|||||||
Reference in new issue
Block a user