Skip to content

[bug fix] driver_vive: fix two crashes in the USB transfer callback (continued) - #383

Open
martijnvb-manus wants to merge 3 commits into
collabora:masterfrom
martijnvb-manus:bugfix/no-free-inflight
Open

martijnvb-manus wants to merge 3 commits into
collabora:masterfrom
martijnvb-manus:bugfix/no-free-inflight

Conversation

@martijnvb-manus

Copy link
Copy Markdown

Continuation of #372.

beaucasque and others added 3 commits October 20, 2026 11:51
…sfer

On a non-COMPLETED status, handle_transfer() retries by calling
libusb_submit_transfer(). When that call SUCCEEDS it fell through to
'goto disconnect', and from there into the shutdown: label, which runs
libusb_free_transfer() on the transfer that was just resubmitted and is
therefore in flight.

libusb_free_transfer() destroys the transfer's mutex, but libusb still has
the transfer in its flying-transfers list. The next event-loop pass locks
that destroyed mutex, pthread_mutex_lock() fails, and usbi_mutex_lock()
(os/threads_posix.h) asserts -- the process aborts:

    Warning: 2.703381 T23 Device disconnect: 1
    python: ../../libusb/os/threads_posix.h:46: usbi_mutex_lock:
            Assertion `pthread_mutex_lock(mutex) == 0' failed.

Observed by unplugging one of four wired Vive trackers while running: the
process dies the instant the device goes away. Also reproducible through
transaction-translator saturation on a single-TT USB 2.0 hub, where a
transfer fails a couple of seconds after start.

Return after a successful resubmit, so the retry actually gets a chance to
run and the in-flight transfer is left alone. Only a failed resubmit still
goes to shutdown:, which is correct -- nothing is in flight then.

This also drops a duplicated increment: error_count was incremented twice
per error (once on its own line, once inside the condition), so the retry
budget was five failures rather than the ten the code reads as.
…he transfer callback

handle_transfer() is libusb's transfer-completion callback: it runs on the
event thread while libusb holds the current transfer's locks. On a
non-COMPLETED status it reached survive_disconnect_device(), which called
survive_close_usb_device() synchronously. That function calls
libusb_cancel_transfer() on every interface -- including the one whose
callback is currently executing -- and survive_config_cancel() reaches
libusb the same way.

Re-entering libusb from inside its own callback makes pthread_mutex_lock()
fail, so usbi_mutex_lock() (os/threads_posix.h) asserts and the process
aborts:

    Warning: 2.703381 T23 Device disconnect: 1
    python: ../../libusb/os/threads_posix.h:46: usbi_mutex_lock:
            Assertion `pthread_mutex_lock(mutex) == 0' failed.

Reproduced consistently with four full-speed Vive trackers behind a
single-TT USB 2.0 hub: transaction-translator saturation makes a transfer
fail about 2.7 s after start, and the process dies every time. Three
trackers on the same hub ran for hours without an error. Moving to a
multi-TT hub removes the trigger but not the latent bug -- any failing
transfer reaches this path, including an optical dropout or a tracker
powering off.

survive_disconnect_device() now only marks the interfaces down and raises
request_disconnect. The poll loop in survive_usb_poll() consumes it and
calls survive_close_usb_device() off-callback, where no libusb lock is
held. This mirrors the existing request_close mechanism, which is already
consumed from the same loop -- the deferral scheme was there, this path
simply did not use it.

The transfer being handled is still cleaned up as before by the shutdown:
label at the end of handle_transfer(), so per-transfer accounting
(active_transfers, request_close) is unchanged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant