From 53815ad8bc2de7d889a02d0d9ebea1a98467bce1 Mon Sep 17 00:00:00 2001 From: Martino Ferrari Date: Sun, 4 Oct 2026 10:11:02 +0200 Subject: [PATCH] fix a GLib critical from cancelling the pick probe's timeout The lifetime fix in ffec797 removes the UDPSCOPE_PROBE_PICK_MS timeout after the nested loop returns, so a row picked before the delay elapsed cannot leave an armed source pointing at a gone stack frame. But the probe returns G_SOURCE_REMOVE on every path, so on the ordinary run -- the probe fires, picks row 0 and the loop quits -- the id was already stale and the removal printed "Source ID N was not found when attempting to remove it" on every probe run. The id moves onto the Chooser and pick_probe clears it on entry, so the cancellation applies only to a probe that never fired. Co-Authored-By: Claude Opus 4.6 --- src/source_dialog.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/source_dialog.cpp b/src/source_dialog.cpp index 5fa8505..0111f65 100644 --- a/src/source_dialog.cpp +++ b/src/source_dialog.cpp @@ -60,6 +60,10 @@ struct Chooser { GtkWidget* form_group = nullptr; GtkWidget* form_iface = nullptr; GtkWidget* form_connect = nullptr; + /// `UDPSCOPE_PROBE_PICK_MS`'s timeout, kept so a pick made before it fires can cancel it. The + /// probe clears it when it runs: `g_source_remove()` on an id whose source already returned + /// `G_SOURCE_REMOVE` is a GLib critical, not a no-op. + guint pick_source = 0; }; void chooser_finish(Chooser& c, bool chosen) { @@ -294,6 +298,10 @@ gboolean on_chooser_key(GtkEventControllerKey*, guint keyval, guint, GdkModifier /// `UDPSCOPE_PROBE_FIELDS_MS` is in `ui.cpp`. gboolean pick_probe(gpointer user) { Chooser& c = *static_cast(user); + // Every path below returns `G_SOURCE_REMOVE`, so the source is gone the moment this returns and + // its id is stale. Clear it here rather than on each path: `choose_source()`'s cancel-the-pending + // -probe call would otherwise be a GLib critical on every run that let the probe fire. + c.pick_source = 0; // `UDPSCOPE_PROBE_NEWHOST=host:port` drives the form instead of a row. if (const char* spec = g_getenv("UDPSCOPE_PROBE_NEWHOST")) { @@ -397,16 +405,15 @@ bool choose_source(Config& cfg) { // Keep the timeout id so it can be cancelled if the chooser finishes before it fires. A row // clicked before UDPSCOPE_PROBE_PICK_MS elapses would otherwise leave an armed source on the // default main context pointing at the now-gone stack frame. - guint pick_probe_id = 0; if (const char* env = g_getenv("UDPSCOPE_PROBE_PICK_MS")) { - pick_probe_id = g_timeout_add(static_cast(std::atoi(env)), pick_probe, &c); + c.pick_source = g_timeout_add(static_cast(std::atoi(env)), pick_probe, &c); } c.loop = g_main_loop_new(nullptr, FALSE); g_main_loop_run(c.loop); g_main_loop_unref(c.loop); c.loop = nullptr; - if (pick_probe_id != 0) g_source_remove(pick_probe_id); + if (c.pick_source != 0) g_source_remove(c.pick_source); return c.chosen; }