From cd974ea4c32b2a6a8a7125654faa469b039d9561 Mon Sep 17 00:00:00 2001 From: Alexandros Evangelou Date: Tue, 1 Sep 2026 12:07:04 +0300 Subject: [PATCH] fix: stop Arduino communicator thread before closing serial port The _communicator thread was never signalled to stop, so cleanup() closed the serial port while the thread kept polling ser.in_waiting on the dead file descriptor, raising OSError: [Errno 9] Bad file descriptor. cleanup() now sets thread_end and joins the thread before ser.close(). Also move thread_end and msg_queue from class attributes to instance attributes: as class attributes they were shared process-wide, so a second Arduino instance would inherit an already-set thread_end and its communicator would exit immediately. The class-level callbacks=True was dead code, always shadowed by the instance attribute set in Interface.__init__. The communicator thread is now a daemon, so if a wedged readline() ever makes the join time out, the leftover thread cannot block interpreter shutdown. Co-Authored-By: Claude Opus 5 --- src/ethopy/interfaces/Arduino.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/ethopy/interfaces/Arduino.py b/src/ethopy/interfaces/Arduino.py index ef9563b..0f6832f 100644 --- a/src/ethopy/interfaces/Arduino.py +++ b/src/ethopy/interfaces/Arduino.py @@ -19,8 +19,6 @@ class Arduino(Interface): - thread_end, msg_queue, callbacks = threading.Event(), PriorityQueue(maxsize=1), True - def __init__(self, **kwargs): if not globals()["IMPORT_SERIAL"]: raise ImportError( @@ -37,9 +35,11 @@ def __init__(self, **kwargs): self.timeout = 0.001 self.no_response = False self.timeout_timer = time.time() + self.thread_end = threading.Event() + self.msg_queue = PriorityQueue(maxsize=1) self.ser = Serial(self.port, baudrate=self.baud) time.sleep(1) - self.thread_runner = threading.Thread(target=self._communicator) + self.thread_runner = threading.Thread(target=self._communicator, daemon=True) self.thread_runner.start() def give_liquid(self, port, duration=False): @@ -94,6 +94,15 @@ def off_proximity(self): return not self.position.state def cleanup(self): + """Stop the communicator thread and close the serial connection. + + The thread must be stopped before the serial port is closed. + """ + self.thread_end.set() + if self.thread_runner.is_alive(): + self.thread_runner.join(timeout=2) + if self.thread_runner.is_alive(): + log.warning("Arduino communicator thread did not stop in time.") self.ser.close() # Close the Serial connection def setup_touch_exit(self):