Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do not fill terminal with noise when a device is surprise disconnected #452

Merged
merged 4 commits into from
Oct 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions software/glasgow/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,10 @@ async def wait_for_sigint():
task.cancel()
await asyncio.wait(tasks, return_when=asyncio.ALL_COMPLETED)

# If the applet task has raised an exception, retrieve it here in case any of the await
# statements above will fail; if we don't, asyncio will unnecessarily complain.
applet_task.exception()

if do_trace:
await device.write_register(target.analyzer.addr_done, 1)
await analyzer_task
Expand Down
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
23 changes: 18 additions & 5 deletions software/glasgow/support/task_queue.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,25 @@ def submit(self, coro):
async def cancel(self):
"""
Cancel all tasks in the queue, and wait until cancellation is finished.

After cancelling them, waits until all the pending tasks become finished, and then awaits
every finished task, ignoring cancellation errors. If some of the tasks raised an exception
before or during being cancelled, the first exception will be re-raised, and the other ones
will be consumed.
"""
for task in self._live:
task.cancel()
if self._live:
await asyncio.wait(self._live, return_when=asyncio.ALL_COMPLETED)
self._done.clear()
exception = None
while self._done:
task = self._done.popleft()
if task.cancelled():
continue
if task.exception() is not None and exception is None:
exception = task.exception()
if exception is not None:
raise exception

async def poll(self):
"""
Expand Down Expand Up @@ -82,12 +95,12 @@ async def wait_all(self):
await self.poll()

def __bool__(self):
"""Check whether there are any pending tasks in the queue."""
return bool(self._live)
"""Check whether there are any tasks in the queue."""
return bool(self._live) or bool(self._done)

def __len__(self):
"""Count pending tasks in the queue."""
return len(self._live)
"""Count tasks in the queue."""
return len(self._live) + len(self._done)

@property
def total_wait_time(self):
Expand Down
Loading