usb-host: exhausted open retries prevent fast re-enumerated devices from reconnecting
Host environment
- Deterministic source-level reproduction: macOS, arm64, Clang 21, GLib 2.90.
- QEMU source:
615ece3c406b262996e569f29ea6eed0d81c4d8d(11.1.50). - The same autoscan function is present in the v11.0.0 release.
- A full
qemu-system-x86_64build with libusb 1.0.30 was also compiled. - No guest image or physical USB device is required for the attached fixture.
Description of problem
usb_host_auto_check() stores an open-failure counter on the QEMU
USBHostDevice, rather than on a particular enumerated host device.
Successful opens leave that counter unchanged. Once it reaches three,
matching devices are skipped before an open is attempted.
The counter is reset only when an inventory scan sees no matching device. A device that disconnects and re-enumerates between scans can therefore inherit the exhausted retry count of its previous incarnation, even if its USB address changed and it is now openable. The exhausted object keeps matching, so subsequent scans do not recover it.
A minimal synthetic sequence is:
- Device A is attached. On disconnect, an attempted open of the stale A fails. A replacement B appears without an intervening empty scan and attaches successfully; the counter remains one.
- Repeat with B and replacement C; the counter remains two.
- C disconnects and its stale open fails. Replacement D appears and is openable, but QEMU never attempts to open it because the counter is three.
Expected: the new enumerated device receives an opportunity to attach. Actual: no open attempt occurs until a scan sees no match or the QEMU USB object is recreated. Three consecutive failures followed by a new device also reproduce the problem; successful intervening connections are not required.
The original retry-limit commit 3ee886c5ba77a65d6b2c2a372a091d6796ed502b explicitly intended unplug/replug to restore the retry budget.
Steps to reproduce
Download and extract qemu-usb-reconnect-repro.zip.
Prerequisites: Python 3, a C compiler named cc, pkg-config, and GLib
headers/libraries. No QEMU checkout, network access, guest image, or physical
USB device is needed. From the extracted directory run:
python3 run-repro.py --source upstream-host-libusb.cThe archive includes the unmodified upstream source file from the pinned revision, the fixture, the runner, expected output, and file checksums.
The runner extracts the actual autoscan and unrealize function bodies from
that revision and compiles them against deterministic fake inventory,
open/close, timer, and device-lifetime boundaries. It does not duplicate the
retry algorithm. All device identities are synthetic (1234:5678, bus 1).
Relevant output:
TEST repeated-reconnects
reconnect 1: replacement attempted=yes attached=yes
reconnect 2: replacement attempted=yes attached=yes
reconnect 3: replacement attempted=no attached=no
expectation failed: state.dh == &devices[i]
FAIL repeated-reconnectsThe full fixture exits with status 1: 15 scenarios, seven failing expectations. These include replacement after retry exhaustion, address reuse, recovery across successful connections, and independent matching devices. Existing controls cover bounded retries for a continuously failing device and recovery after an empty scan.
Additional information
Source locations:
This is a source-level reproduction of retry policy. It does not exercise physical USB, actual libusb event scheduling, or guest traffic. It does not identify why a physical device disconnected. The full binary build is a separate compile check, not a claim of physical reconnect validation.
Automated-tool disclosure: AI assistance was used for source analysis, fixture generation, and this report. An AI coding agent executed the fixture against both the unmodified source and a local experimental correction, and checked the reported results. The reporter authorized submission; we do not claim a separate manual execution by the reporter. The local generated patch is not being proposed for upstream acceptance under QEMU's current code-provenance policy.