diff --git a/src/driver_vive.c b/src/driver_vive.c index 005cfff8..9425797b 100755 --- a/src/driver_vive.c +++ b/src/driver_vive.c @@ -447,6 +447,9 @@ struct SurviveUSBInfo { struct survive_config_packet *cfg_user; bool request_close, request_reopen; + /* Raised from the libusb transfer-completion callback, consumed by the + poll loop off-callback. See survive_disconnect_device(). */ + bool request_disconnect; }; struct SurviveViveData { @@ -1031,6 +1034,13 @@ int survive_vive_usb_poll(SurviveContext *ctx, void *v) { survive_config_poll(usbInfo); + /* Close requested from a transfer callback: do it HERE, on the poll + thread, where no libusb lock is held. */ + if (usbInfo->request_disconnect) { + usbInfo->request_disconnect = false; + survive_close_usb_device(usbInfo); + } + if (survive_handle_close_request_flag(usbInfo)) { i--; } diff --git a/src/driver_vive.libusb.h b/src/driver_vive.libusb.h index 25007de1..61bd3366 100644 --- a/src/driver_vive.libusb.h +++ b/src/driver_vive.libusb.h @@ -204,8 +204,25 @@ static int survive_open_usb_device(SurviveViveData *sv, survive_usb_device_t d, static inline void survive_close_usb_device(struct SurviveUSBInfo *usbInfo); static void survive_disconnect_device(SurviveUSBInterface *iface) { + struct SurviveUSBInfo *usbInfo = iface->usbInfo; iface->ctx = 0; - survive_close_usb_device(iface->usbInfo); + + /* We are inside handle_transfer(), i.e. inside libusb's + transfer-completion callback: the event thread holds the current + transfer's locks. survive_close_usb_device() calls + libusb_cancel_transfer() on every interface -- including the one being + handled -- and survive_config_cancel() reaches libusb the same way. + Re-entering libusb from here makes pthread_mutex_lock() fail, and + usbi_mutex_lock() (os/threads_posix.h) asserts: abort(). + + Only mark here. The poll loop calls survive_close_usb_device() + off-callback, where no lock is held. The transfer being handled is + still cleaned up as before by the shutdown: label below. */ + for (size_t j = 0; j < usbInfo->interface_cnt; j++) { + usbInfo->interfaces[j].shutdown = 1; + usbInfo->interfaces[j].assoc_obj = 0; + } + usbInfo->request_disconnect = true; } static void handle_transfer(struct libusb_transfer *transfer) { uint64_t time = OGGetAbsoluteTimeUS(); @@ -227,11 +244,18 @@ static void handle_transfer(struct libusb_transfer *transfer) { if (!iface->shutdown && transfer->status != LIBUSB_TRANSFER_COMPLETED) { SV_WARN("%f %s Device disconnect: %d", survive_run_time(ctx), survive_colorize_codename(iface->assoc_obj), transfer->status); - iface->error_count++; if (iface->error_count++ < 10) { if (libusb_submit_transfer(transfer)) { goto shutdown; } + + /* Resubmit succeeded: the transfer is IN FLIGHT again. Falling + through to shutdown: would call libusb_free_transfer() on it, + which destroys its mutex while libusb still holds it in the + flying-transfers list. The next event-loop pass then locks a + destroyed mutex and usbi_mutex_lock() asserts. Return and let + the retry run its course. */ + return; } goto disconnect;