Field incident (.41 VM, 2026-08-15): under keep_alive=off the restore debounce is 0 s, so the restore worker pops the deadline before a reconnect's cancel arrives — clearing PENDING_RESTORE then cancels nothing, and do_restore_tv_session runs concurrently with the new session's takeover. The takeover found the autologin unit inactive (the restore hadn't restarted it yet), so dm_plan correctly refused to stop the DM for a dead unit and went mask-only — and the in-flight restore then restarted sddm underneath that mask at 10:36:39.638. On Bazzite, sddm's helper execs the session script directly, so the mask stops only the final unit start, never the retry: 1,272 relogins in ~4 minutes, a fresh Steam launch each round, ~170k PIDs, load 7+, until a manual systemctl stop sddm.
The fix serializes the two with a RESTORE_FLIGHT mutex: the worker pops the deadline and runs the restore under it, and cancel_pending_restore (which create_managed_session now routes through instead of a bare PENDING_RESTORE clear) blocks until an in-flight restore completes. Either the cancel wins — no restore starts, warm reuse exactly as before — or the restore wins and the connect takes over a fully restored box, where the autologin unit is live again and the DM stop engages through the ordinary plan. restore_takeover_now (host shutdown) takes the same lock so the two restore entry points can't interleave either. Lock order: RESTORE_FLIGHT is outermost, taken only at those three entry points.
Regression test: reconnect_cancel_waits_out_an_in_flight_restore pins the property (a cancel must not race past a running restore, and must complete once it releases).
Gate: rebased onto current main (post-#245); xcheck linux check+clippy clean; pf-vdisplay 237/237 in the rust:1.96 container.
Field incident (.41 VM, 2026-08-15): under `keep_alive=off` the restore debounce is 0 s, so the restore worker pops the deadline before a reconnect's cancel arrives — clearing `PENDING_RESTORE` then cancels nothing, and `do_restore_tv_session` runs **concurrently** with the new session's takeover. The takeover found the autologin unit inactive (the restore hadn't restarted it yet), so `dm_plan` correctly refused to stop the DM for a dead unit and went mask-only — and the in-flight restore then restarted sddm **underneath that mask** at 10:36:39.638. On Bazzite, sddm's helper execs the session script directly, so the mask stops only the final unit start, never the retry: **1,272 relogins in ~4 minutes**, a fresh Steam launch each round, ~170k PIDs, load 7+, until a manual `systemctl stop sddm`.
The fix serializes the two with a `RESTORE_FLIGHT` mutex: the worker pops the deadline **and** runs the restore under it, and `cancel_pending_restore` (which `create_managed_session` now routes through instead of a bare `PENDING_RESTORE` clear) blocks until an in-flight restore completes. Either the cancel wins — no restore starts, warm reuse exactly as before — or the restore wins and the connect takes over a fully restored box, where the autologin unit is live again and the DM stop engages through the ordinary plan. `restore_takeover_now` (host shutdown) takes the same lock so the two restore entry points can't interleave either. Lock order: `RESTORE_FLIGHT` is outermost, taken only at those three entry points.
Regression test: `reconnect_cancel_waits_out_an_in_flight_restore` pins the property (a cancel must not race past a running restore, and must complete once it releases).
Gate: rebased onto current main (post-#245); xcheck linux check+clippy clean; pf-vdisplay **237/237** in the rust:1.96 container.
Under keep_alive=off the restore debounce is 0 s, so the worker pops the
deadline before a reconnect's cancel arrives — clearing PENDING_RESTORE
then cancels nothing, and the restore runs concurrently with the new
takeover. Field trace (.41, 2026-08-15): the new takeover listed the
autologin unit inactive (the restore hadn't restarted it yet), so
dm_plan correctly refused to stop the DM for a dead unit and went
mask-only — and the in-flight restore then restarted sddm underneath
that mask at 10:36:39.638. SDDM's helper execs the session script
directly, so the mask stops only the final unit start, never the retry:
1272 relogins in ~4 minutes, a fresh Steam launch each round, load 7+,
until a manual 'systemctl stop sddm'.
RESTORE_FLIGHT now serializes the two: the worker pops the deadline and
runs do_restore_tv_session under it, and cancel_pending_restore (which
create_managed_session now routes through instead of clearing
PENDING_RESTORE bare) blocks until an in-flight restore completes. Either
the cancel wins — no restore starts, warm reuse as before — or the
restore wins and the connect takes over a fully restored box, where the
autologin unit is live again and the DM stop engages through the
ordinary plan. restore_takeover_now (host shutdown) takes the same lock
so the two restore entry points can't interleave either.
Gate: xcheck linux check+clippy clean; pf-vdisplay 237/237 in the
rust:1.96 container, including the new regression test.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Field incident (.41 VM, 2026-08-15): under
keep_alive=offthe restore debounce is 0 s, so the restore worker pops the deadline before a reconnect's cancel arrives — clearingPENDING_RESTOREthen cancels nothing, anddo_restore_tv_sessionruns concurrently with the new session's takeover. The takeover found the autologin unit inactive (the restore hadn't restarted it yet), sodm_plancorrectly refused to stop the DM for a dead unit and went mask-only — and the in-flight restore then restarted sddm underneath that mask at 10:36:39.638. On Bazzite, sddm's helper execs the session script directly, so the mask stops only the final unit start, never the retry: 1,272 relogins in ~4 minutes, a fresh Steam launch each round, ~170k PIDs, load 7+, until a manualsystemctl stop sddm.The fix serializes the two with a
RESTORE_FLIGHTmutex: the worker pops the deadline and runs the restore under it, andcancel_pending_restore(whichcreate_managed_sessionnow routes through instead of a barePENDING_RESTOREclear) blocks until an in-flight restore completes. Either the cancel wins — no restore starts, warm reuse exactly as before — or the restore wins and the connect takes over a fully restored box, where the autologin unit is live again and the DM stop engages through the ordinary plan.restore_takeover_now(host shutdown) takes the same lock so the two restore entry points can't interleave either. Lock order:RESTORE_FLIGHTis outermost, taken only at those three entry points.Regression test:
reconnect_cancel_waits_out_an_in_flight_restorepins the property (a cancel must not race past a running restore, and must complete once it releases).Gate: rebased onto current main (post-#245); xcheck linux check+clippy clean; pf-vdisplay 237/237 in the rust:1.96 container.