View Issue Details

IDProjectCategoryView StatusLast Update
0010319ardourbugspublic2026-05-08 16:44
ReporterW6EL Assigned To 
PrioritynormalSeverityminorReproducibilityalways
Status newResolutionopen 
PlatformUbuntuOSLinuxOS Version(any)
Product Version9.2 
Summary0010319: Crash on exit with Contour ShuttlePro, libusb tear-down issue
DescriptionHi 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 Reproduce1. 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 InformationI'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.
TagsNo tags attached.

Activities

W6EL

2026-05-08 16:44

reporter  

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;
 }
changes.patch (1,612 bytes)   

Issue History

Date Modified Username Field Change
2026-05-08 16:44 W6EL New Issue
2026-05-08 16:44 W6EL File Added: changes.patch