Skip to content

Commit

Permalink
device.hardware: improve handling of surprise disconnection.
Browse files Browse the repository at this point in the history
This commit adds handling of the LIBUSB_ERROR_NO_DEVICE error during
transfer submission. This error was already handled during transfer
completion, but since surprise disconnection is (by definition)
asynchronous, this it can happen right before submission too.

The message is also changed from ambiguous "device lost" (lost where?)
to unambiguous "device disconnected".

The code in _do_transfer with the exception handler nested three levels
deep is some of the most cursed and convoluted logic I have ever written
but as far as I can tell it is reasonably appropriate to the task. Sigh.

This is still not quite enough to reduce the errors printed for
a surprise disconnection to a single line, but we're getting there.
  • Loading branch information
whitequark committed Oct 21, 2023
1 parent fd0931c commit b402e98
Showing 1 changed file with 15 additions and 9 deletions.
24 changes: 15 additions & 9 deletions software/glasgow/device/hardware.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,23 +254,29 @@ def usb_callback(transfer):
elif status == usb1.TRANSFER_STALL:
result_future.set_exception(usb1.USBErrorPipe())
elif status == usb1.TRANSFER_NO_DEVICE:
result_future.set_exception(GlasgowDeviceError("device lost"))
result_future.set_exception(GlasgowDeviceError("device disconnected"))
else:
result_future.set_exception(GlasgowDeviceError(
"transfer error: {}".format(usb1.libusb1.libusb_transfer_status(status))))

def handle_usb_error(func):
try:
func()
except usb1.USBErrorNoDevice:
raise GlasgowDeviceError("device disconnected") from None

loop = asyncio.get_event_loop()
transfer.setCallback(lambda transfer: loop.call_soon_threadsafe(usb_callback, transfer))
transfer.submit()
handle_usb_error(lambda: transfer.submit())
try:
return await result_future
except asyncio.CancelledError:
try:
transfer.cancel()
await cancel_future
except usb1.USBErrorNotFound:
pass # already finished, one way or another
raise
finally:
if result_future.cancelled():
try:
handle_usb_error(lambda: transfer.cancel())
await cancel_future
except usb1.USBErrorNotFound:
pass # already finished, one way or another

async def control_read(self, request_type, request, value, index, length):
logger.trace("USB: CONTROL IN type=%#04x request=%#04x "
Expand Down

0 comments on commit b402e98

Please sign in to comment.