View Issue Details
| ID | Project | Category | View Status | Date Submitted | Last Update |
|---|---|---|---|---|---|
| 0010319 | ardour | bugs | public | 2026-05-08 16:44 | 2026-05-08 16:44 |
| Reporter | W6EL | Assigned To | |||
| Priority | normal | Severity | minor | Reproducibility | always |
| Status | new | Resolution | open | ||
| Platform | Ubuntu | OS | Linux | OS Version | (any) |
| Product Version | 9.2 | ||||
| Summary | 0010319: Crash on exit with Contour ShuttlePro, libusb tear-down issue | ||||
| Description | Hi All, Recently built Ardour from source, works great, but when the Contour ShuttlePro V2 is connected, Ardour crashes on exit: ../libusb/os/threads_posix.h:46: usbi_mutex_lock: Assertion `pthread_mutex_lock(mutex) == 0' failed. We had similar issues with wfview (an open source project I work on). The fix is to flush any transfers and then release the interface in a specific order. | ||||
| Steps To Reproduce | 1. Connect a Contour ShuttlePro V2 over USB 2. Open Ardour from a terminal 3. Open a saved session 4. Close Ardour 5. Observe "../libusb/os/threads_posix.h:46: usbi_mutex_lock: Assertion `pthread_mutex_lock(mutex) == 0' failed." in the console output. | ||||
| Additional Information | I'm attaching a simple patch to fix the issue (changes.patch). I used Claude to help navigate the codebase and develop some of the patch. The fix is very simple and should work fine on other platforms. It would not surprise me if some of the other supported controllers need similar fixes, but I have not checked. My OS is Ubuntu 24, kernel 6.17.0-19-generic, on x64 hardware. This patch is licensed for usage in Ardour under the same license as the rest of Ardour's code. | ||||
| Tags | No tags attached. | ||||
|
|
changes.patch (1,612 bytes)
diff --git a/libs/surfaces/contourdesign/contourdesign.cc b/libs/surfaces/contourdesign/contourdesign.cc
index bddddf6da6..0368a49651 100644
--- a/libs/surfaces/contourdesign/contourdesign.cc
+++ b/libs/surfaces/contourdesign/contourdesign.cc
@@ -369,9 +369,31 @@ ContourDesignControlProtocol::release_device ()
return;
}
+ /* The interrupt transfer is perpetually re-submitted in handle_event(),
+ * so a transfer is in flight at this point. Cancel it and drain libusb
+ * events ourselves so the callback runs (status = CANCELLED) and the
+ * transfer is no longer in flight before we touch the handle:
+ * - libusb_close() on a handle with an in-flight transfer is UB
+ * - libusb_release_interface() after libusb_close() is a use-after-
+ * free of dev_handle->lock and trips usbi_mutex_lock on exit.
+ * The BaseUI thread that normally pumps events has already been told
+ * to quit (stop() ran first), so we drive completion synchronously.
+ */
+ DEBUG_TRACE (DEBUG::ContourDesignControl, "release_device: cancel pending transfer\n");
+ if (_usb_transfer) {
+ libusb_cancel_transfer (_usb_transfer);
+ for (int i = 0; i < 20; ++i) {
+ struct timeval tv = { 0, 25000 }; /* 25 ms */
+ if (libusb_handle_events_timeout_completed (NULL, &tv, NULL) < 0) {
+ break;
+ }
+ }
+ }
+
+ DEBUG_TRACE (DEBUG::ContourDesignControl, "release_device: release_interface + close\n");
+ libusb_release_interface (_dev_handle, 0);
libusb_close (_dev_handle);
libusb_free_transfer (_usb_transfer);
- libusb_release_interface (_dev_handle, 0);
_usb_transfer = 0;
_dev_handle = 0;
}
|