Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: GlasgowEmbedded/glasgow
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 675633334285
Choose a base ref
...
head repository: GlasgowEmbedded/glasgow
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: a7d041f4ca58
Choose a head ref
  • 2 commits
  • 2 files changed
  • 1 contributor

Commits on Jul 28, 2020

  1. Copy the full SHA
    2917a10 View commit details
  2. support.task_queue: improve cancellation support.

      * Propagate cancellation out of poll(), wait_one(), and wait_all().
      * Make cancel() an async function that waits until cancellation
        is finished.
    
    Before this commit, cancelling a demultiplexer read (e.g. with ^C)
    that was waiting on USB would cause it to fail an assert. After this
    commit, the read is also cancelled.
    whitequark committed Jul 28, 2020
    Copy the full SHA
    a7d041f View commit details
Showing with 16 additions and 19 deletions.
  1. +7 −10 software/glasgow/access/direct/demultiplexer.py
  2. +9 −9 software/glasgow/support/task_queue.py
17 changes: 7 additions & 10 deletions software/glasgow/access/direct/demultiplexer.py
Original file line number Diff line number Diff line change
@@ -23,10 +23,10 @@
# On the other hand, we would like to prevent programs from submitting a
# lot of small URBs and using up all the DMA-able kernel memory. [...]
#
# In other words, there is be a platform-specific limit for USB I/O size, which is not
# discoverable via libusb, and hitting which does not result in a sensible error returned
# from libusb (it returns LIBUSB_ERROR_IO even though USBDEVFS_SUBMITURB ioctl correctly
# returns -ENOMEM), so it is not even possible to be optimistic and back off after hitting it.
# In other words, there is a platform-specific limit for USB I/O size, which is not discoverable
# via libusb, and hitting which does not result in a sensible error returned from libusb
# (it returns LIBUSB_ERROR_IO even though USBDEVFS_SUBMITURB ioctl correctly returns -ENOMEM),
# so it is not even possible to be optimistic and back off after hitting it.
#
# To deal with this, use requests of at most 1024 EP buffer sizes (512 KiB with the FX2) as
# an arbitrary cutoff, and hope for the best.
@@ -54,7 +54,7 @@
# a single fixed value works.
_packets_per_xfer = 32

# Queue as many transfers as we can, but no more than 10, as the returns beyond that point
# Queue as many transfers as we can, but no more than 16, as the returns beyond that point
# are diminishing.
_xfers_per_queue = min(16, _max_packets_per_ep // _packets_per_xfer)

@@ -180,11 +180,8 @@ def __init__(self, device, applet, mux_interface,
async def cancel(self):
if self._in_tasks or self._out_tasks:
self.logger.trace("FIFO: cancelling operations")
self._in_tasks .cancel()
self._out_tasks.cancel()
# libusb cancellation is asynchronous, so wait until it's actually done.
await self._in_tasks .wait_all()
await self._out_tasks.wait_all()
await self._in_tasks .cancel()
await self._out_tasks.cancel()

async def reset(self):
await self.cancel()
18 changes: 9 additions & 9 deletions software/glasgow/support/task_queue.py
Original file line number Diff line number Diff line change
@@ -21,8 +21,7 @@ def __init__(self):

def _callback(self, future):
self._live.remove(future)
if not future.cancelled():
self._done.append(future)
self._done.append(future)

def submit(self, coro):
"""
@@ -33,16 +32,18 @@ def submit(self, coro):
self._live.add(future)
future.add_done_callback(self._callback)

def cancel(self):
async def cancel(self):
"""
Cancel all tasks in the queue.
Cancel all tasks in the queue, and wait until cancellation is finished.
"""
for task in self._live:
task.cancel()
if self._live:
await asyncio.wait(self._live, return_when=asyncio.ALL_COMPLETED)

async def poll(self):
"""
Await all finished tasks that have been submitted to the queue. Return ``True`` if there
Await all finished tasks that have been submitted to the queue. Returns ``True`` if there
were any finished tasks, ``False`` otherwise.
This method needs to be called regularly to ensure that exceptions are propagated upwards
@@ -56,20 +57,19 @@ async def poll(self):
async def wait_one(self):
"""
Await at least one task in the queue. If there are no finished tasks, waits until the first
pending task finishes. Equivalent to :meth:`poll` otherwise.
pending task finishes.
"""
if not self._done:
await asyncio.wait(self._live, return_when=asyncio.FIRST_COMPLETED)
assert len(self._done) > 0
return await self.poll()
await self.poll()

async def wait_all(self):
"""
Await all tasks in the queue, if any.
"""
if self._live:
await asyncio.wait(self._live, return_when=asyncio.ALL_COMPLETED)
return await self.poll()
await self.poll()

def __bool__(self):
return bool(self._live)