From b402e985c9919612df856e7f31357045adbdf488 Mon Sep 17 00:00:00 2001 From: Catherine Date: Sat, 21 Oct 2023 16:10:20 +0000 Subject: [PATCH] device.hardware: improve handling of surprise disconnection. 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. --- software/glasgow/device/hardware.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/software/glasgow/device/hardware.py b/software/glasgow/device/hardware.py index 18b9cc39e..56953898e 100644 --- a/software/glasgow/device/hardware.py +++ b/software/glasgow/device/hardware.py @@ -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 "