ALSA: automatically reconnect USB MIDI inputs and outputs - #374
Conversation
Unplugging a USB MIDI device removes its ALSA sequencer subscriptions. Plugging it back in does not restore an application's open connections, even when the device returns with the same ALSA client address. A new address also makes reconnecting by the old numeric address unreliable. Restore identifiable USB connections within the ALSA backend, without requiring applications to close and reopen their RtMidi objects. Public behavior --------------- - Add supportsAutoReconnect(), setAutoReconnect(), and isAutoReconnectEnabled() to the C++ API. Recovery defaults on for ALSA builds with the required support; other backends report unsupported. - Disabling recovery preserves an existing subscription. Re-enabling it also checks for a device lost while recovery was disabled. - closePort() cancels recovery. isPortOpen() continues to describe the application's open connection, including temporary device absence. Identity and recovery --------------------- - Match USB vendor/product, serial number, and MIDI port number/name. Without a serial, match the USB location instead. Reject ambiguous identities and do not reconnect software ports by name alone. - Allow the matching device to return at a different ALSA client address. - Reconcile subscriptions after announcement overflow or monitor restart. Retry recoverable monitor, enumeration, and subscription failures. - Reset incomplete SysEx and input timestamp state on unsubscribe so a disconnected device's partial message cannot contaminate later input. Threading and compatibility -------------------------- - Share one announcement client and worker across all input/output ports. Per-connection helpers retain the monitor through shared ownership. - Protect registry state and subscription lifetime with one mutex. Normal MIDI input and output do not acquire this mutex. Unregister before freeing subscriptions, and join the worker outside the lock. - Report background failures through std::cerr, without invoking an application's error callback from the monitor. Retry transient worker startup failures; report persistent failure on the calling thread. - Keep the helper implementation private to RtMidi.cpp. Compile USB identity support only with ALSA 1.1.1 or newer; older builds use a stub. - Use non-virtual API extensions and existing backend identification. Do not add public data members or alter existing virtual-table slots. Tests and build integration --------------------------- - Add real ALSA sequencer tests with simulated USB identities and test-only linker wrappers for fault injection. No MIDI hardware is needed, and the wrappers are never linked into the production library. - Cover input/output delivery, reused and changed addresses, wrong and ambiguous identities, runtime controls, incomplete SysEx, concurrent port lifecycles, one shared worker, overflow, and failure recovery. - Exercise the capability API for both directions and the dummy backend. - Compile and run the regression test in native ALSA CMake and Autotools builds. Compile but do not execute it while cross-compiling. Report a skip when the sequencer or required ALSA support is unavailable. - Include the test sources, runners, and documentation in distributions. Validation and limitations -------------------------- CMake and Autotools regression runs pass locally. Address/undefined behavior sanitizer testing and a comparison of existing virtual tables were also performed during development. ThreadSanitizer reports existing ALSA input-thread shutdown races, reproduced against unmodified upstream; this patch does not address those separate races. Physical USB hotplug testing passed in a rebuilt GrandOrgue using a Roland VK-88 through a UM-880. MIDI input recovered after unplugging and reconnecting the UM-880, with the external reconnect watcher disabled. Physical MIDI-output recovery has not yet been verified; output recovery is covered by the automated ALSA sequencer tests. USB-location fallback cannot distinguish an identical replacement in the same socket. Enumeration remains a snapshot, and lost MIDI messages are not replayed. Ordinary patchbay unsubscribes alone do not trigger recovery, but reconciliation after lost announcements restores open intent. See tests/README-hotplug.md for test details and remaining limitations.
|
Pretty nice feature and very very useful in the GrandOrgue community! Thanks a lot ✨ @garyscavone could you please take a look at this changes? We would love to have it working in the next release of rtmidi ❤️ |
|
Reviewed and tested this on a second machine — Linux, alsa-lib 1.2.14, real USB MIDI hardware. Summary: it works, it fixes a failure that is currently silent, and we could not find a problem with it. Details below, then a few questions and some future-direction notes. What we checked
Physical hotplug testYou noted output recovery had not been verified on hardware, so we tested that specifically — real mains removal via a switched outlet, not a USB-level reset. Method: open one output and one input on a KMI malletStation Pro, then send a Universal Identity Request once a second and wait 600 ms for the reply. A round trip is the only reliable signal here, since a send during the outage raises nothing. The port is opened once and never reopened. Logged RtMidi's This branch: 16 s of replies at 5 ms, device removed, 16 s of misses, power restored, first reply 7 s later, then 88 consecutive replies with no gaps. Unmodified master, same device and same 10 s outage: identical up to the outage, then 59 consecutive misses — it never recovered. From the moment the device returned, So the bug reproduces against master and this branch fixes it. One caveat: in both runs the device returned at the same ALSA client address, so we exercised the same-address path. The changed-address path is covered by your test suite, not by ours. Questions before merge
None of these block it from our point of view. Future direction — not requests for this PRThree things this opens up, mentioned only as context since the state is already tracked internally here: A disconnect/reconnect notification. Recovery is currently invisible to the application: no notification, The same mechanism on macOS and Windows. No backend has any hotplug hook today, so this is the first. macOS looks like the easiest follow-up: We would be glad to collaborate on either, rather than duplicate work: we have macOS and Windows hardware and can test, and we are happy to prototype the callback against this branch if it is of interest. Your design, your call on shape — just say if you would rather keep them. |
|
Sorry for the late update here. I found actually that this PR only fixes half of my issue, which is which Grandorgue, that consumes rtmidi. If I start GO with my MIDI interface powered on, the rtmidi fix allows subsequently powering off the interface, and back on, and this is completely transparent for GO, and presumably for other rtmidi apps as well. However, it does not fix the case where GO starts with the MIDI interface disconnect. In that case, since GO never opens it at startup, the rtmidi auto-reconnect does not fix. Unfortunately, that requires a change at the application level, ie. in GO. |
|
You are correct that at the application level you're still going to need to detect the device and make the initial connection, but there is still value in having the reconnect built into RtMidi. I think we should merge this into the next major release along with some functions that expose the metadata that's now available in WMS and CoreMIDI, things like serial numbers and usb VID and PID, but that would be a separate effort/PR. I'm holding off on merging any significant PRs until @garyscavone has time to review and weigh in. |
Thanks. Of course, I did not mean to withdraw the PR. I think the fix should go in. I was just pointing out that it only solved 50% of my issue. |
|
As mentioned, I don't have time to support RtMidi. I have added Eric as a developer with administrative privileges and I'm happy to add other interested parties as well. My normal suggestions would be: 1. avoid making the code complicated and hard to support; and 2. try to keep all aspects of the API working across all supported MIDI subsystems. This particular PR seems a bit limited on the last point but hopefully there are ways to get similar functionality working for the other subsystems in the future. Feel free to approve it. |
|
Thanks @garyscavone, I'll merge this and moving forward will use my best judgement. |
Unplugging a USB MIDI device removes its ALSA sequencer subscriptions. Plugging it back in does not restore an application's open connections, even when the device returns with the same ALSA client address. A new address also makes reconnecting by the old numeric address unreliable.
Restore identifiable USB connections within the ALSA backend, without requiring applications to close and reopen their RtMidi objects.
Public behavior
Identity and recovery
Threading and compatibility
Tests and build integration
Validation and limitations
CMake and Autotools regression runs pass locally. Address/undefined behavior sanitizer testing and a comparison of existing virtual tables were also performed during development. ThreadSanitizer reports existing ALSA input-thread shutdown races, reproduced against unmodified upstream; this patch does not address those separate races.
Physical USB hotplug testing passed in a rebuilt GrandOrgue using a Roland VK-88 through a UM-880. MIDI input recovered after unplugging and reconnecting the UM-880, with the external reconnect watcher disabled. Physical MIDI-output recovery has not yet been verified; output recovery is covered by the automated ALSA sequencer tests.
USB-location fallback cannot distinguish an identical replacement in the same socket. Enumeration remains a snapshot, and lost MIDI messages are not replayed. Ordinary patchbay unsubscribes alone do not trigger recovery, but reconciliation after lost announcements restores open intent.
See tests/README-hotplug.md for test details and remaining limitations.