There is a race between handle_timeout() and the completion functions.
When one thread is in handle_timeout() and another thread wakes
up from a poll(), there exists a window where the transfer has been
cancelled, but USBI_TRANSFER_TIMED_OUT is not yet set in timeout_flags.
Therefore, usbi_handle_transfer_completion() is sometimes called
with LIBUSB_TRANSFER_CANCELLED instead of the expected
LIBUSB_TRANSFER_TIMED_OUT.
timeout_flags is protected by the flying_transfers_lock, this commit
makes usbi_handle_transfer_cancellation() take that lock before
checking for USBI_TRANSFER_TIMED_OUT in timeout_flags, fixing this.
Reported-by: Joost Muller <joostmuller@gmail.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
* will attempt to take the lock. */
int usbi_handle_transfer_cancellation(struct usbi_transfer *transfer)
{
+ struct libusb_context *ctx = ITRANSFER_CTX(transfer);
+ uint8_t timed_out;
+
+ usbi_mutex_lock(&ctx->flying_transfers_lock);
+ timed_out = transfer->timeout_flags & USBI_TRANSFER_TIMED_OUT;
+ usbi_mutex_unlock(&ctx->flying_transfers_lock);
+
/* if the URB was cancelled due to timeout, report timeout to the user */
- if (transfer->timeout_flags & USBI_TRANSFER_TIMED_OUT) {
+ if (timed_out) {
usbi_dbg("detected timeout cancellation");
return usbi_handle_transfer_completion(transfer, LIBUSB_TRANSFER_TIMED_OUT);
}
-#define LIBUSB_NANO 11135
+#define LIBUSB_NANO 11137