Bound serial close so a dead port cannot hang the run

When a USB-serial adapter disappears mid-run, writes to the open handle
fail with PermissionError, but serial.close() can block forever. Cleanup
then wedged after the second port closed and Ctrl+C could not break out,
because the main thread was stuck inside a blocking Windows call.

Close on a daemon thread and give up after CLOSE_TIMEOUT_S, so an
abandoned port can never hold up the exit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-23 15:11:36 +08:00
co-authored by Claude Opus 5
parent c0ca523143
commit 74a7b98691
+12
View File
@@ -56,6 +56,7 @@ import minimalmodbus
SLAVE_ID = 0x10 # all four devices share slave id 16 SLAVE_ID = 0x10 # all four devices share slave id 16
BAUDRATE = 9600 BAUDRATE = 9600
SERIAL_TIMEOUT = 2.0 # seconds SERIAL_TIMEOUT = 2.0 # seconds
CLOSE_TIMEOUT_S = 3.0 # give up on a port that will not close
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -217,6 +218,17 @@ class Device:
return reads return reads
def close(self): def close(self):
# A USB adapter unplugged mid-run wedges serial.close() forever, and
# Ctrl+C cannot break a blocking Windows call. Close on a daemon
# thread so a dead port can never hold up the exit.
closer = threading.Thread(target=self._close_serial, daemon=True)
closer.start()
closer.join(CLOSE_TIMEOUT_S)
if closer.is_alive():
logger.warning("%s: %s did not close in %.0fs, abandoning it",
self.name, self.port, CLOSE_TIMEOUT_S)
def _close_serial(self):
try: try:
self.inst.serial.close() self.inst.serial.close()
logger.info("%s disconnected", self.name) logger.info("%s disconnected", self.name)