python-libusb1 icon indicating copy to clipboard operation
python-libusb1 copied to clipboard

USBTransfer finalize issue

Open WeiminWangKolmostar opened this issue 1 year ago • 3 comments

import usb1

VENDOR_ID = 0x04b4
PRODUCT_ID = 0x0008

STREAM_IO_INTERFACE = 3
STREAM_IN_ENDPOINT = 0x85

with usb1.USBContext() as context:
    handle = context.openByVendorIDAndProductID(
        VENDOR_ID,
        PRODUCT_ID,
        skip_on_error=True,
    )
    if handle is None:
        # Device not present, or user is not allowed to access device.
        raise RuntimeError("Could not open device.")

    with handle.claimInterface(STREAM_IO_INTERFACE):
        # Build a list of transfer objects and submit them to prime the pump.
        transfer_list = []
        for _ in range(4):
            transfer = handle.getTransfer()
            transfer.setBulk(
                usb1.ENDPOINT_IN | STREAM_IN_ENDPOINT,
                1024,
                callback=print,
            )
            transfer.submit()
            transfer_list.append(transfer)

        # handle.getTransfer()  # uncomment this line will cause segment fault
        for t in transfer_list:
            t.cancel()

In above code, if I don't cancel the transfers, the program will hang on exit. By using Ctrl-C, I can see the program blocks on https://github.com/vpelletier/python-libusb1/blob/master/usb1/init.py#L1083 , and then it blocks on https://github.com/vpelletier/python-libusb1/blob/master/usb1/init.py#L307

Another issue is that if transfer.setBulk() is never invoked after create the transfer, segment fault will occur.

WeiminWangKolmostar avatar Aug 02 '24 09:08 WeiminWangKolmostar

The bug to cause the hang on issue:

If user didn't cancel the transfer, during the USBDeviceHandle finalizing, the transfer.cancel() actually not work since the interface is released (USBErrorNoDevice will be raised when try to cancel transfer). So the while inflight loop will never exit.

https://github.com/vpelletier/python-libusb1/blob/a03f4c3171d55a8325accc6b79246c235f0a6ce5/usb1/init.py#L1073-L1085

WeiminWangKolmostar avatar Sep 20 '24 09:09 WeiminWangKolmostar

As for the segment fault error, seems a libusb bug in macOS. See https://github.com/libusb/libusb/issues/1059

WeiminWangKolmostar avatar Sep 20 '24 09:09 WeiminWangKolmostar

Thank you for the very detailed bug report.

If user didn't cancel the transfer, during the USBDeviceHandle finalizing, the transfer.cancel() actually not work since the interface is released (USBErrorNoDevice will be raised when try to cancel transfer). So the while inflight loop will never exit.

This code makes the assumption that every submitted transfer (libusb_submit_transger called and returned 0) will eventually have its callback executed, whatever the outcome of the transfer:

  • completed successfully
  • cancelled (while cancellation is meaningful, so in your example it is before releasing the interface)
  • completed with an error (device unplugged, ...)

In the case you describe, this seems to not happen when you release the interface. I think in this case the kernel is the piece of code responsible for completing all in-flight transfers with an error in such case, and that libusb would then "just work": it would get the transfer completion from the kernel, call the transfer callback, which would remove it from the inflight set.

...unless libusb actively does something to prevent that from happening when releasing the interface.

Could you try increasing libusb verbosity to try to see what is happening at its level ? See USBContext.setDebug.

vpelletier avatar Sep 21 '24 19:09 vpelletier

Closing issue for lack of feedback.

vpelletier avatar Dec 28 '24 11:12 vpelletier