| **PR-5** #151 repro + quiesce-then-repoint fix | **CLOSED 2026-08-02 — both bugs Deck-validated fixed. Issue #151 closed on GitHub.** `tests/probe-node-swap.sh` (Tier 1) confirmed the kernel-level node-swap precondition is real and forceable (plain 2-pad: 3/3 clean). `tests/probe-reconnect-swap.sh` (Tier 2) then reproduced #151 live in a real 4-up `MCSS_CONTROLLER_PROXY=1` session — real EBUSY / "capabilities... different than expected" evsieve errors, matching the issue's own trace exactly. **Fix**: `controller_proxy.sh` gained `proxy_quiesce_slot()` (removes the disconnecting slot's proxy-pads symlink instead of leaving it stale), wired into `orchestrator.sh`'s `CONTROLLER_REMOVE` handler *before* `slot_release` — closes the window where a slot's `evsieve` (persist=reopen) could grab whatever pad the kernel handed the freed `eventN` to next. Unit test T16 (`test_controller_proxy.sh`) and two new assertions (`test_reconnect_dispatch.sh`) added, both mutation-tested (broke the fix, confirmed red, restored, confirmed green). CI baselines bumped. **Deck-validated 2026-08-02, 3/3 iterations: zero EBUSY/grab-failure signatures** — the original race is closed. Two rounds of probe-timing fixes were needed first (operator directly observed the forced disconnect firing before the 4th window had settled, and evsieve's own logs showed a udev-uaccess-tagging retry loop running tens of seconds — fixed by waiting on `hw_slot_window_visible` + a 15s settle + a 60s symlink-resolution poll instead of guessed sleeps). **New finding surfaced by that same run, same night**: the symlink-resolution check still failed 3/3 — not from residual EBUSY, but because slot 4 (whichever pad reconnects *first* after a simultaneous 2-pad drop) never got its `CONTROLLER_ADD` processed as a RESUME at all. Traced via a full `set -x` debug-log capture (`/tmp/splitscreen-debug-latest.log`, courtesy `SPLITSCREEN_DEBUG_LOG`): `controller_monitor.sh` detects and logs *both* simultaneous removals (`Controller removed: /dev/input/event20` and `.../event31`, back to back), but only **one** `CONTROLLER_REMOVE` message actually reaches the orchestrator's FIFO reader — confirmed via `_handle_msg`'s own trace, which never sees the second one. That slot's `disconnected` flag stays `false`, so its reconnect's `slot_claim` call sees "already active" and REJECTs it outright (not a grab race — an explicit, logged reject). Root-caused via the same debug log, cross-referenced against `dock_detection.sh`'s pre-existing (and previously unexplained) `# H6: tolerate broken pipe` workaround on its own FIFO writes — same underlying gap, just papered over there. **Fix (commit `03e728b`, same night)**: `orchestrator.sh` gained `_open_fifo_reader()`, which opens `SPLITSCREEN_FIFO` ONCE (`exec {fd}<> "$fifo"`) and holds it for the whole life of `docked_flow`/`handheld_flow`, called before any writer (controller monitor, dock monitor, watchdog) starts; `_read_fifo_msg` reads from that persistent fd (falling back to the old per-call open if it was never set up). Closed in `cleanup()` after all writers are killed. New test T6.9 (`test_orchestrator.sh`) mutation-tested: disabling `_open_fifo_reader` reproduces the loss (8/9, correctly red); restoring it fixes it (9/9, green) — also revealed the load-bearing part is holding *any* persistent fd open, not which fd `_read_fifo_msg` itself reads from. All existing suites (`test_orchestrator.sh` 9/9, `test_reconnect_dispatch.sh` 15/15, `test_controller_proxy.sh` 16/16, `test_watchdog.sh` 15/15) still pass; CI baseline bumped. Mid-investigation the Deck's gamescope session went unresponsive (joysticks/Steam button/shortcut button/touchscreen all dead, SSH still reachable); traced to 4 orphaned `evsieve` proxy processes from an earlier probe run still holding live `MCSS-slot1..4` ghost USB devices (killed by exact PID, per [[no-remote-steam-restart]] no Steam/gamescope restart attempted remotely) — didn't resolve the freeze, operator power-cycled. **Retry #1 after reboot also hit trouble**: Steam itself restarted mid-run (gamescope stayed up throughout — confirmed via process start times, not a gamescope crash). Operator correctly rejected the first read ("Steam doesn't take 30 minutes to settle") and named the real cause: `tests/probe-reconnect-swap.sh` was cycling 4 real JVMs up and down 3x with near-zero pacing between pad connects/disconnects and between iterations — plausibly hard enough on the machine to trip a Steam-side watchdog restart. **Fix (commit `59d0ed7`)**: 2s gaps between each pad connect/disconnect (initial 4-pad creation, and the swap's own drop/reconnect pairs — confirmed this doesn't defeat the swap mechanism, since the kernel hands out the lowest-free node at CREATE time, not destroy time) plus a 20s rest between iterations. **Retry #2, after a second reboot, with the pacing fix: full success.** `tests/probe-reconnect-swap.sh` — **3/3 clean, 0/3 reproduced**, no disruption during the run. All three checks (state-file identity, proxy symlink resolution, evsieve log signature) passed on every iteration, including the exact symlink-resolution failure that had failed 3/3 times on every run before the FIFO fix. A cosmetic `grep -c` double-count bug in the probe's own Check 3 (harmless "0\n0" arithmetic warning, didn't affect the PASS/FAIL result) found and fixed same session (commit `eeddd19`). Orphaned evsieve processes from the run's own teardown lag (a separate, pre-existing "Steam reaper still present" issue, not #151) cleaned up by exact PID after each attempt. | agent ~6h (spent) · **Deck ~1h10min (spent, Tier 1 + Tier 2 + 2 retries) — closed** |
0 commit comments