From 74a7b9869195a495a8850f57975a523e0a5904b9 Mon Sep 17 00:00:00 2001 From: vanminhpe Date: Wed, 23 Sep 2026 15:11:36 +0800 Subject: [PATCH] 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) --- BE-RCU-ovp_test_csv.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/BE-RCU-ovp_test_csv.py b/BE-RCU-ovp_test_csv.py index df34c43..6ccdc7b 100644 --- a/BE-RCU-ovp_test_csv.py +++ b/BE-RCU-ovp_test_csv.py @@ -56,6 +56,7 @@ import minimalmodbus SLAVE_ID = 0x10 # all four devices share slave id 16 BAUDRATE = 9600 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 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: self.inst.serial.close() logger.info("%s disconnected", self.name)