better packet queue logic

This commit is contained in:
Gilles Boccon-Gibod
2024-07-17 17:48:26 -07:00
parent 5aae44b610
commit 6a51166af7

View File

@@ -15,10 +15,10 @@
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Imports # Imports
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
from __future__ import annotations
import asyncio import asyncio
import logging import logging
import threading import threading
import collections
import ctypes import ctypes
import platform import platform
@@ -114,13 +114,17 @@ async def open_usb_transport(spec: str) -> Transport:
self.device = device self.device = device
self.acl_out = acl_out self.acl_out = acl_out
self.acl_out_transfer = device.getTransfer() self.acl_out_transfer = device.getTransfer()
self.packets = collections.deque() # Queue of packets waiting to be sent self.acl_out_transfer_ready = asyncio.Semaphore(1)
self.packets: asyncio.Queue[bytes] = (
asyncio.Queue()
) # Queue of packets waiting to be sent
self.loop = asyncio.get_running_loop() self.loop = asyncio.get_running_loop()
self.queue_task = None
self.cancel_done = self.loop.create_future() self.cancel_done = self.loop.create_future()
self.closed = False self.closed = False
def start(self): def start(self):
pass self.queue_task = asyncio.create_task(self.process_queue())
def on_packet(self, packet): def on_packet(self, packet):
# Ignore packets if we're closed # Ignore packets if we're closed
@@ -132,62 +136,64 @@ async def open_usb_transport(spec: str) -> Transport:
return return
# Queue the packet # Queue the packet
self.packets.append(packet) self.packets.put_nowait(packet)
if len(self.packets) == 1:
# The queue was previously empty, re-prime the pump
self.process_queue()
def transfer_callback(self, transfer): def transfer_callback(self, transfer):
self.acl_out_transfer_ready.release()
status = transfer.getStatus() status = transfer.getStatus()
# pylint: disable=no-member # pylint: disable=no-member
if status == usb1.TRANSFER_COMPLETED: if status == usb1.TRANSFER_CANCELLED:
self.loop.call_soon_threadsafe(self.on_packet_sent)
elif status == usb1.TRANSFER_CANCELLED:
self.loop.call_soon_threadsafe(self.cancel_done.set_result, None) self.loop.call_soon_threadsafe(self.cancel_done.set_result, None)
else: return
if status != usb1.TRANSFER_COMPLETED:
logger.warning( logger.warning(
color(f'!!! OUT transfer not completed: status={status}', 'red') color(f'!!! OUT transfer not completed: status={status}', 'red')
) )
def on_packet_sent(self): async def process_queue(self):
if self.packets: while True:
self.packets.popleft() # Wait for a packet to transfer.
self.process_queue() packet = await self.packets.get()
def process_queue(self): # Wait until we can start a transfer.
if len(self.packets) == 0: await self.acl_out_transfer_ready.acquire()
return # Nothing to do
packet = self.packets[0] # Transfer the packet.
packet_type = packet[0] packet_type = packet[0]
if packet_type == hci.HCI_ACL_DATA_PACKET: if packet_type == hci.HCI_ACL_DATA_PACKET:
self.acl_out_transfer.setBulk( self.acl_out_transfer.setBulk(
self.acl_out, packet[1:], callback=self.transfer_callback self.acl_out, packet[1:], callback=self.transfer_callback
) )
self.acl_out_transfer.submit() self.acl_out_transfer.submit()
elif packet_type == hci.HCI_COMMAND_PACKET: elif packet_type == hci.HCI_COMMAND_PACKET:
self.acl_out_transfer.setControl( self.acl_out_transfer.setControl(
USB_RECIPIENT_DEVICE | USB_REQUEST_TYPE_CLASS, USB_RECIPIENT_DEVICE | USB_REQUEST_TYPE_CLASS,
0, 0,
0, 0,
0, 0,
packet[1:], packet[1:],
callback=self.transfer_callback, callback=self.transfer_callback,
) )
self.acl_out_transfer.submit() self.acl_out_transfer.submit()
else: else:
logger.warning(color(f'unsupported packet type {packet_type}', 'red')) logger.warning(
color(f'unsupported packet type {packet_type}', 'red')
)
def close(self): def close(self):
self.closed = True self.closed = True
if self.queue_task:
self.queue_task.cancel()
async def terminate(self): async def terminate(self):
if not self.closed: if not self.closed:
self.close() self.close()
# Empty the packet queue so that we don't send any more data # Empty the packet queue so that we don't send any more data
self.packets.clear() while not self.packets.empty():
self.packets.get_nowait()
# If we have a transfer in flight, cancel it # If we have a transfer in flight, cancel it
if self.acl_out_transfer.isSubmitted(): if self.acl_out_transfer.isSubmitted():