diff --git a/pycyphal/transport/can/media/candump/_candump.py b/pycyphal/transport/can/media/candump/_candump.py index c68de56d..66e1d85e 100644 --- a/pycyphal/transport/can/media/candump/_candump.py +++ b/pycyphal/transport/can/media/candump/_candump.py @@ -146,7 +146,10 @@ def close(self) -> None: self._f.close() self._thread, thd = None, self._thread assert thd is not None - thd.join(timeout=1) + try: + thd.join(timeout=1) + except RuntimeError: + pass @property def _is_closed(self) -> bool: diff --git a/pycyphal/transport/can/media/pythoncan/_pythoncan.py b/pycyphal/transport/can/media/pythoncan/_pythoncan.py index 119859d1..04f2af2c 100644 --- a/pycyphal/transport/can/media/pythoncan/_pythoncan.py +++ b/pycyphal/transport/can/media/pythoncan/_pythoncan.py @@ -323,9 +323,15 @@ def close(self) -> None: self._closed = True try: self._tx_queue.put(None) - self._tx_thread.join(timeout=self._MAXIMAL_TIMEOUT_SEC * 10) + try: + self._tx_thread.join(timeout=self._MAXIMAL_TIMEOUT_SEC * 10) + except RuntimeError: + pass if self._maybe_thread is not None: - self._maybe_thread.join(timeout=self._MAXIMAL_TIMEOUT_SEC * 10) + try: + self._maybe_thread.join(timeout=self._MAXIMAL_TIMEOUT_SEC * 10) + except RuntimeError: + pass self._maybe_thread = None finally: try: diff --git a/pycyphal/transport/can/media/socketcan/_socketcan.py b/pycyphal/transport/can/media/socketcan/_socketcan.py index 6dad9a06..17d68562 100644 --- a/pycyphal/transport/can/media/socketcan/_socketcan.py +++ b/pycyphal/transport/can/media/socketcan/_socketcan.py @@ -165,7 +165,10 @@ def close(self) -> None: if self._ctl_main.fileno() >= 0: # Ignore if already closed. self._ctl_main.send(b"stop") # The actual data is irrelevant, we just need it to unblock the select(). if self._maybe_thread: - self._maybe_thread.join(timeout=_SELECT_TIMEOUT) + try: + self._maybe_thread.join(timeout=_SELECT_TIMEOUT) + except RuntimeError: + pass self._maybe_thread = None finally: self._sock.close() # These are expected to be idempotent. diff --git a/pycyphal/transport/can/media/socketcand/_socketcand.py b/pycyphal/transport/can/media/socketcand/_socketcand.py index d011d48a..0a382eb8 100644 --- a/pycyphal/transport/can/media/socketcand/_socketcand.py +++ b/pycyphal/transport/can/media/socketcand/_socketcand.py @@ -203,9 +203,15 @@ def close(self) -> None: self._closed = True try: self._tx_queue.put(None) - self._tx_thread.join(timeout=self._MAXIMAL_TIMEOUT_SEC * 10) + try: + self._tx_thread.join(timeout=self._MAXIMAL_TIMEOUT_SEC * 10) + except RuntimeError: + pass if self._maybe_thread is not None: - self._maybe_thread.join(timeout=self._MAXIMAL_TIMEOUT_SEC * 10) + try: + self._maybe_thread.join(timeout=self._MAXIMAL_TIMEOUT_SEC * 10) + except RuntimeError: + pass self._maybe_thread = None finally: try: diff --git a/tests/demo/_demo_app.py b/tests/demo/_demo_app.py index ea7cd802..a3ed15b4 100644 --- a/tests/demo/_demo_app.py +++ b/tests/demo/_demo_app.py @@ -310,7 +310,7 @@ async def _unittest_slow_demo_app( assert hb.health.value == hb.health.NOMINAL assert hb.mode.value == hb.mode.OPERATIONAL assert num_heartbeats <= hb.uptime <= 300 - assert prev_hb_transfer[0].uptime <= hb.uptime <= prev_hb_transfer[0].uptime + 1 + assert prev_hb_transfer[0].uptime <= hb.uptime <= prev_hb_transfer[0].uptime + 2 # +2 due to aliasing assert transfer.transfer_id == prev_hb_transfer[1].transfer_id + 1 prev_hb_transfer = hb_transfer num_heartbeats += 1 diff --git a/tests/transport/can/media/_pythoncan.py b/tests/transport/can/media/_pythoncan.py index e4a31395..4e503cd8 100644 --- a/tests/transport/can/media/_pythoncan.py +++ b/tests/transport/can/media/_pythoncan.py @@ -164,6 +164,7 @@ def _unittest_can_pythoncan_iface_name() -> None: # multiple colons are allowed in interface names, only the first one is split media = PythonCANMedia("virtual:0:0", 1_000_000) assert media.interface_name == "virtual:0:0" + media.close() def _unittest_can_pythoncan_errors() -> None: