From b89010375459d0a905462a03724cae1c7a6a6da0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Colomb?= Date: Tue, 26 May 2026 10:18:58 +0200 Subject: [PATCH 1/2] emcy: Fix maximum timeout logic errors in EmcyConsumer.wait(). If an EMCY package was received, but filtered out by the emcy_code matching, the condition waiting is started again with the same timeout. Thus the actual maximum waiting time is not correctly limited to the given argument, as the docstring promises. Track the remaining time until the initial deadline instead, as basis for the condition wait. Further, spurious wake-ups from the condition wait on the OS level are not handled correctly. The threading.Condition docs explicitly recommend checking the shared state (number of logged entries in this case) in the while loop, because the wait() call may abort early. The current code assumes that this necessarily indicates a timeout, without checking the actually passed time again. Reorder the check against end_time in the loop to avoid this. --- canopen/emcy.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/canopen/emcy.py b/canopen/emcy.py index 6a860f07..f690fba3 100644 --- a/canopen/emcy.py +++ b/canopen/emcy.py @@ -69,16 +69,19 @@ def wait( while True: with self.emcy_received: prev_log_size = len(self.log) - self.emcy_received.wait(timeout) + remaining = end_time - time.time() + if remaining <= 0: + return None # Actual timeout reached + self.emcy_received.wait(remaining) if len(self.log) == prev_log_size: - # Resumed due to timeout - return None + if time.time() >= end_time: + # Resumed due to timeout + return None + else: + continue # Get last logged EMCY emcy = self.log[-1] logger.info("Got %s", emcy) - if time.time() > end_time: - # No valid EMCY received on time - return None if emcy_code is None or emcy.code == emcy_code: # This is the one we're interested in return emcy From ff9af572cd00a349b072bbf32cc4cf7855df7461 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Colomb?= Date: Mon, 29 Jun 2026 22:03:44 +0200 Subject: [PATCH 2/2] Test simulated spurious condition wake-up. --- test/test_emcy.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/test_emcy.py b/test/test_emcy.py index b4b54a19..788ad720 100644 --- a/test/test_emcy.py +++ b/test/test_emcy.py @@ -86,6 +86,10 @@ def test_emcy_consumer_wait(self): def push_err(): emcy.on_emcy(0x81, b'\x01\x20\x01\x01\x02\x03\x04\x05', 100) + def no_err(): + with emcy.emcy_received: + emcy.emcy_received.notify_all() + def check_err(err): self.assertIsNotNone(err) self.check_error( @@ -103,6 +107,10 @@ def check_err(err): ): check_err(emcy.wait(timeout=TIMEOUT)) + # Check unfiltered wait, spurious condition wake-up. + with mock_rx_thread(emcy, no_err): + self.assertIsNone(emcy.wait(timeout=TIMEOUT)) + # Check filtered wait, on success. with ( self.assertLogs(level=logging.INFO),