diff --git a/i2rt/motor_drivers/dm_driver.py b/i2rt/motor_drivers/dm_driver.py index 1ef69541..3d22024e 100644 --- a/i2rt/motor_drivers/dm_driver.py +++ b/i2rt/motor_drivers/dm_driver.py @@ -464,6 +464,7 @@ def __init__( self.command_lock = threading.RLock() self.start_thread_flag = False + self._control_thread: Optional[threading.Thread] = None if start_thread: self.start_thread() @@ -532,8 +533,8 @@ def start_thread(self) -> None: if self.start_thread_flag: return logging.info("starting separate thread for control loop") - thread = threading.Thread(target=self._set_torques_and_update_state) - thread.start() + self._control_thread = threading.Thread(target=self._set_torques_and_update_state) + self._control_thread.start() self.start_thread_flag = True time.sleep(0.1) while self.state is None: @@ -748,6 +749,13 @@ def get_same_bus_device_states(self) -> Any: def close(self) -> None: self.running = False + # Give the control loop a chance to see running=False and fall out + # before its CAN socket is closed underneath it. Without this it can + # be inside _set_commands when the socket goes, and dies with + # "file descriptor cannot be a negative integer (-1)" on every + # shutdown. The timeout keeps close() bounded if the loop is wedged. + if self._control_thread is not None: + self._control_thread.join(timeout=1.0) self.motor_interface.close()