You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Found while sweeping #631 (the RefCell-borrow-held-across-a-GTK-call pattern). #631's fix covered nine sites; grepping the same two shapes turns up more, unfixed, outside that issue's file list. Filing rather than fixing — same as how #632/#638 spun out of earlier sweeps.
A RefCell borrow held across a GTK call that can emit a signal. If the emission is ever synchronous and the handler borrows the same cell, it panics — and a panic unwinding through a glib callback aborts the process. Reachability is not verified either way for any of these; that's the same standard #631 used, not a downgrade.
Form A — the borrow spans a loop of GTK teardown calls (.remove() / .destroy())
crates/hytte-ui/src/popup.rs:286 (close_catchers) — for win in catchers.borrow_mut().drain(..) { win.destroy(); }. Notable because it's in hytte-ui, not trollshell, and because fix(hytte-ui): destroy() the three remaining close()-teardown sites (#638) #639 already touched this exact function (converting close() → destroy()) without addressing this borrow.
trollshell/src/components/reactive_list.rs:88 — for row in rows_track.borrow_mut().drain(..) { container_for_bind.remove_row(&row); }, inside the shared generic list-diff helper (RowContainer/bind_rows). This one backs 8 consumers (panels/{appearance,vpn,clipboard,displays,stats}.rs, panels/network/{wired,wifi,connection}.rs), so it's the highest-blast-radius instance found.
trollshell/src/panels/stats.rs:632 and trollshell/src/components/power_profile.rs:49 — same shape, each inside a bind() reactive closure with its own hand-rolled rows_track.
trollshell/src/widgets/calendar.rs:328 — for (_d, row) in rows_track.borrow_mut().drain(..) { group.remove(&row); }.
trollshell/src/widgets/tasks.rs:139 — for row in rows_track.borrow_mut().drain(..) { group.remove(&row); }.
trollshell/src/panels/connections.rs:67-72 — two sites, owned/others, structured as a named let mut owned = owned_for_bind.borrow_mut(); bound across the loop rather than an anonymous drain() temporary — same hazard, just spelled with an explicit binding instead of a chained call.
crates/trollshell-control-center/src/main.rs:449 (clear_rows) — for row in state.rows.borrow_mut().drain(..) { state.group.remove(&row); }. In the control-center binary, not the shell — the pattern isn't scoped to trollshell/src/overlays/.
Form B — if let Some(x) = cell.borrow_mut().take() with a GTK call in the then-block
trollshell/src/widgets/calendar.rs:331 — if let Some(p) = placeholder_track.borrow_mut().take() { group.remove(&p); }, immediately after the Form A site above in the same function.
trollshell/src/widgets/tasks.rs:142 and :145 — placeholder_track and overflow_track, same shape, same function as the Form A site above.
trollshell/src/panels/connections.rs:75 — overflow_for_bind, same shape, same function as the two Form A sites above.
Not sites (checked, ruled out)
Grepping .borrow_mut().take() and .borrow_mut().drain() more broadly also turns up several look-alikes that are not hazards, for the same reason #631 excluded prompt.rs's abort_subscription (no GTK call in the body) or because they use let-else (whose scrutinee temporary drops at the end of the let statement, not across a following GTK call, unlike a same-statement if let then-block):
trollshell/src/overlays/fullscreen.rs:60, trollshell/src/components/focus.rs:146, crates/trollshell-control-center/src/main.rs:114, trollshell/src/plugins/mod.rs:651, crates/hytte-ui/src/app.rs:80,95 — each either aborts a task/timer (no GTK) or uses let-else.
Recommendation
Same as #631: for the Form A sites, prefer RefCell::take() (or, where the container also needs post-drain access like connections.rs's named bindings, bind-then-loop) over holding the borrow across the removal calls; for Form B, bind-then-act. One mechanical PR per the cluster convention, reactive_list.rs first given its blast radius.
Found while sweeping #631 (the
RefCell-borrow-held-across-a-GTK-call pattern). #631's fix covered nine sites; grepping the same two shapes turns up more, unfixed, outside that issue's file list. Filing rather than fixing — same as how #632/#638 spun out of earlier sweeps.The pattern (unchanged from #631/#630/#627)
A
RefCellborrow held across a GTK call that can emit a signal. If the emission is ever synchronous and the handler borrows the same cell, it panics — and a panic unwinding through a glib callback aborts the process. Reachability is not verified either way for any of these; that's the same standard #631 used, not a downgrade.Form A — the borrow spans a loop of GTK teardown calls (
.remove()/.destroy())crates/hytte-ui/src/popup.rs:286(close_catchers) —for win in catchers.borrow_mut().drain(..) { win.destroy(); }. Notable because it's inhytte-ui, nottrollshell, and because fix(hytte-ui): destroy() the three remaining close()-teardown sites (#638) #639 already touched this exact function (convertingclose()→destroy()) without addressing this borrow.trollshell/src/components/reactive_list.rs:88—for row in rows_track.borrow_mut().drain(..) { container_for_bind.remove_row(&row); }, inside the shared generic list-diff helper (RowContainer/bind_rows). This one backs 8 consumers (panels/{appearance,vpn,clipboard,displays,stats}.rs,panels/network/{wired,wifi,connection}.rs), so it's the highest-blast-radius instance found.trollshell/src/panels/stats.rs:632andtrollshell/src/components/power_profile.rs:49— same shape, each inside abind()reactive closure with its own hand-rolledrows_track.trollshell/src/widgets/calendar.rs:328—for (_d, row) in rows_track.borrow_mut().drain(..) { group.remove(&row); }.trollshell/src/widgets/tasks.rs:139—for row in rows_track.borrow_mut().drain(..) { group.remove(&row); }.trollshell/src/panels/connections.rs:67-72— two sites,owned/others, structured as a namedlet mut owned = owned_for_bind.borrow_mut();bound across the loop rather than an anonymousdrain()temporary — same hazard, just spelled with an explicit binding instead of a chained call.crates/trollshell-control-center/src/main.rs:449(clear_rows) —for row in state.rows.borrow_mut().drain(..) { state.group.remove(&row); }. In the control-center binary, not the shell — the pattern isn't scoped totrollshell/src/overlays/.Form B —
if let Some(x) = cell.borrow_mut().take()with a GTK call in the then-blocktrollshell/src/widgets/calendar.rs:331—if let Some(p) = placeholder_track.borrow_mut().take() { group.remove(&p); }, immediately after the Form A site above in the same function.trollshell/src/widgets/tasks.rs:142and:145—placeholder_trackandoverflow_track, same shape, same function as the Form A site above.trollshell/src/panels/connections.rs:75—overflow_for_bind, same shape, same function as the two Form A sites above.Not sites (checked, ruled out)
Grepping
.borrow_mut().take()and.borrow_mut().drain()more broadly also turns up several look-alikes that are not hazards, for the same reason #631 excludedprompt.rs'sabort_subscription(no GTK call in the body) or because they uselet-else(whose scrutinee temporary drops at the end of theletstatement, not across a following GTK call, unlike a same-statementif letthen-block):trollshell/src/overlays/fullscreen.rs:60,trollshell/src/components/focus.rs:146,crates/trollshell-control-center/src/main.rs:114,trollshell/src/plugins/mod.rs:651,crates/hytte-ui/src/app.rs:80,95— each either aborts a task/timer (no GTK) or useslet-else.Recommendation
Same as #631: for the Form A sites, prefer
RefCell::take()(or, where the container also needs post-drain access likeconnections.rs's named bindings, bind-then-loop) over holding the borrow across the removal calls; for Form B, bind-then-act. One mechanical PR per the cluster convention,reactive_list.rsfirst given its blast radius.Refs #631, #630, #627, #632, #638.