From 68e2935d9b4fe5a33954aa526dcfd39c0d71ae97 Mon Sep 17 00:00:00 2001 From: Dmitry Ilyin Date: Sun, 19 Apr 2026 22:36:29 +0300 Subject: [PATCH] Guard serial read against pyserial blocking indefinitely MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wrap run_in_executor with asyncio.wait_for so that if pyserial's read() blocks despite the timeout (e.g. device reboots mid-transfer), the protocol fails cleanly instead of hanging forever. The guard timeout is 2x the pyserial timeout + 1s, so it only fires when pyserial itself is stuck. Tested on hi3516av300 hardware — no interference with normal operation. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/defib/transport/serial.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/defib/transport/serial.py b/src/defib/transport/serial.py index f88ba2c..6d9116b 100644 --- a/src/defib/transport/serial.py +++ b/src/defib/transport/serial.py @@ -67,12 +67,17 @@ async def read(self, size: int, timeout: float | None = None) -> bytes: old_timeout = self._port.timeout try: self._port.timeout = timeout - data = await asyncio.get_event_loop().run_in_executor( + coro = asyncio.get_event_loop().run_in_executor( None, self._port.read, size ) + # Guard against pyserial blocking despite the timeout + guard = timeout * 2 + 1.0 if timeout is not None else None + data = await asyncio.wait_for(coro, timeout=guard) if not data and timeout is not None: raise TransportTimeout(f"Read timeout ({timeout}s)") return bytes(data) + except asyncio.TimeoutError: + raise TransportTimeout(f"Read timeout ({timeout}s, asyncio guard)") finally: self._port.timeout = old_timeout