From cca9d6e7191f25d5cd717e7be917465b643b7456 Mon Sep 17 00:00:00 2001 From: jafrivacation Date: Mon, 29 Jun 2026 13:18:47 -0400 Subject: [PATCH] Fix SocketCAN send timeout semantics --- can/interfaces/socketcan/socketcan.py | 10 ++++------ test/test_socketcan.py | 16 +++++++++++++++- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 6dc856cbf..aaeb152fa 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -841,7 +841,7 @@ def send(self, msg: Message, timeout: float | None = None) -> None: :param msg: A message object. :param timeout: Wait up to this many seconds for the transmit queue to be ready. - If not given, the call may fail immediately. + If not given, wait indefinitely. :raises ~can.exceptions.CanError: if the message could not be written. @@ -851,13 +851,10 @@ def send(self, msg: Message, timeout: float | None = None) -> None: logger_tx.debug("sending: %s", msg) started = time.time() - # If no timeout is given, poll for availability - if timeout is None: - timeout = 0 time_left = timeout data = build_can_frame(msg) - while time_left >= 0: + while time_left is None or time_left >= 0: # Wait for write availability ready = select.select([], [self.socket], [], time_left)[1] if not ready: @@ -869,7 +866,8 @@ def send(self, msg: Message, timeout: float | None = None) -> None: return # Not all data were sent, try again with remaining data data = data[sent:] - time_left = timeout - (time.time() - started) + if timeout is not None: + time_left = timeout - (time.time() - started) raise can.CanOperationError("Transmit buffer full") diff --git a/test/test_socketcan.py b/test/test_socketcan.py index 9d042f425..a5e33e02e 100644 --- a/test/test_socketcan.py +++ b/test/test_socketcan.py @@ -9,7 +9,7 @@ import sys import unittest import warnings -from unittest.mock import patch +from unittest.mock import MagicMock, patch import can from can.interfaces.socketcan.constants import ( @@ -21,6 +21,7 @@ ) from can.interfaces.socketcan.socketcan import ( BcmMsgHead, + SocketcanBus, bcm_header_factory, build_bcm_header, build_bcm_transmit_header, @@ -36,6 +37,19 @@ def setUp(self): self._ctypes_sizeof = ctypes.sizeof self._ctypes_alignment = ctypes.alignment + @patch("can.interfaces.socketcan.socketcan.select.select") + def test_send_without_timeout_blocks_indefinitely(self, select): + bus = SocketcanBus.__new__(SocketcanBus) + bus.channel = "can0" + bus.socket = MagicMock() + bus._send_once = MagicMock(side_effect=[1, 15]) + select.return_value = ([], [bus.socket], []) + + bus.send(can.Message(arbitration_id=0x123, data=[1, 2, 3, 4])) + + self.assertEqual(2, select.call_count) + self.assertTrue(all(call.args[3] is None for call in select.call_args_list)) + @unittest.skipIf(sys.version_info >= (3, 14), "Fails on Python 3.14 or newer") @patch("ctypes.sizeof") @patch("ctypes.alignment")