v0.32.1: a stuck Rabbit connection can no longer freeze the window

Opening Connections called find_device() on the GUI thread, which lists the
gvfs-MTP mount; on a wedged mount that blocks forever in an uninterruptible
FUSE wait and the app had to be force-quit. DeviceWatcher now probes on a
daemon thread (one at a time, STUCK after 3 s, late answers still land), the
menu reads its cached answer, and a stuck mount shows "Rabbit isn't
responding…" with a Reset Connection button (kill gvfsd-mtp, remount via
gio). Tests can no longer reach the real mount (conftest patches
find_device), which also takes the suite from ~200 s to ~22 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-01 00:58:26 -07:00
co-authored by Claude Opus 5.5
parent ba795e3c69
commit 27abdbdb63
6 changed files with 382 additions and 18 deletions
+14 -5
View File
@@ -440,11 +440,20 @@ persistence) → GUI (Qt widgets that read the manager and connect to its signal
is copied, writing nothing when nothing moved. `PlayJournal.load` then
treats the R1 as one more machine.
**A wedged gvfs-MTP mount blocks in uninterruptible FUSE waits**: `timeout`
can't kill a process stuck on it, and `find_device()` — called on the GUI
thread every time the Connections menu opens, and by the GUI tests —
freezes with it. USB re-enumeration (lock/unlock with `mtp,adb`, an
`adb install`) is what wedged it in Round 51. Recovery: `kill` the
`gvfsd-mtp` process, then `gio mount mtp://<device>/`.
can't kill a process stuck on it. USB re-enumeration (lock/unlock with
`mtp,adb`, an `adb install`) is what wedged it in Round 51. Recovery:
`kill` the `gvfsd-mtp` process, then `gio mount mtp://<device>/` —
`device_sync.reset_mtp_connection`, offered as **Reset Connection** when
the menu item reads "Rabbit isn't responding…". Since Round 71 **the GUI
thread never calls `find_device()`**: it froze the window every time the
Connections menu opened on a wedged mount. `MainWindow._devices`
(`device_sync.DeviceWatcher`) probes on a daemon thread, one at a time,
and declares `STUCK` after 3 s; click handlers go through
`when_ready(callback)`. A stuck probe thread can't be killed, so quitting
then may still hang until the reset. `tests/conftest.py` patches
`find_device` to `None` for every test, and nothing — test or tool call —
may list `/run/user/*/gvfs`; ask `gio mount -l` instead, which goes over
D-Bus.
- **`lintunes/export/`** — `File → Export Playlist…` (also on a playlist's
right-click menu). A sibling of `device_sync`, reusing its filename helpers