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 <noreply@anthropic.com>
This commit is contained in:
Martino FerrariandClaude Opus 4.6 committed 2026-10-04 10:11:02 +02:00
1 parent ffec797462
commit 53815ad8bc
1 file changed
+10 -3
+10 -3
View File
@@ -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<Chooser*>(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<guint>(std::atoi(env)), pick_probe, &c);
c.pick_source = g_timeout_add(static_cast<guint>(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;
}