Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions can/interfaces/socketcan/socketcan.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
At the end of the file the usage of the internal methods is shown.
"""

# pylint: disable=too-many-lines
import ctypes
import ctypes.util
import errno
import logging
import select
Expand Down Expand Up @@ -786,6 +786,10 @@ def __init__(
# so this is always supported by the kernel
self.socket.setsockopt(socket.SOL_SOCKET, constants.SO_TIMESTAMPNS, 1)

# Create epoll instance and register socket for read and write events
self._epoll = select.epoll()
self._epoll.register(self.socket.fileno(), select.EPOLLIN | select.EPOLLOUT)

try:
bind_socket(self.socket, channel)
kwargs.update(
Expand All @@ -810,14 +814,19 @@ def shutdown(self) -> None:
for channel, bcm_socket in self._bcm_sockets.items():
log.debug("Closing bcm socket for channel %s", channel)
bcm_socket.close()
if not self._epoll.closed:
self._epoll.unregister(self.socket.fileno())
self._epoll.close()
log.debug("Closing raw can socket")
self.socket.close()

def _recv_internal(self, timeout: float | None) -> tuple[Message | None, bool]:
try:
# get all sockets that are ready (can be a list with a single value
# being self.socket or an empty list if self.socket is not ready)
ready_receive_sockets, _, _ = select.select([self.socket], [], [], timeout)
epoll_timeout = timeout if timeout is not None else -1
events = self._epoll.poll(epoll_timeout)
ready_receive_sockets = any(event & select.EPOLLIN for _, event in events)
except OSError as error:
# something bad happened (e.g. the interface went down)
raise can.CanOperationError(
Expand Down Expand Up @@ -859,7 +868,8 @@ def send(self, msg: Message, timeout: float | None = None) -> None:

while time_left >= 0:
# Wait for write availability
ready = select.select([], [self.socket], [], time_left)[1]
events = self._epoll.poll(time_left)
ready = any(event & select.EPOLLOUT for _, event in events)
if not ready:
# Timeout
break
Expand Down
1 change: 1 addition & 0 deletions doc/changelog.d/2053.fixed.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Replaced select.select() with select.epoll() in SocketcanBus
32 changes: 32 additions & 0 deletions test/test_socketcan.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@
"""

import ctypes
import os
import resource
import struct
import sys
import tempfile
import unittest
import warnings
from contextlib import ExitStack
from unittest.mock import patch

import can
Expand Down Expand Up @@ -390,6 +394,34 @@ def test_pypy_socketcan_support(self):
"See: https://github.com/pypy/pypy/issues/3808"
)

@unittest.skipUnless(TEST_INTERFACE_SOCKETCAN, "Only run when vcan0 is available")
@unittest.skipUnless(
resource.getrlimit(resource.RLIMIT_NOFILE)[0] > 1024,
"Only run when the system supports high file limit",
)
def test_high_socket_fileno(self):
"""send() and recv() succeed when socket fileno > 1023."""
num_open_files = len(os.listdir("/proc/self/fd"))
num_files_to_open = 1024 - num_open_files
with ExitStack() as stack:
for _ in range(num_files_to_open):
stack.enter_context(tempfile.TemporaryFile())

bus1 = can.Bus(interface="socketcan", channel="vcan0")
bus2 = can.Bus(interface="socketcan", channel="vcan0")
msg = can.Message(
is_extended_id=False,
arbitration_id=0x100,
data=[1, 2, 3, 4, 5, 6, 7, 8],
)

timeout = 1.0 if IS_PYPY else 0.1
bus1.send(msg)
recv_msg = bus2.recv(timeout)
self.assertIsNotNone(recv_msg)
self.assertEqual(recv_msg.arbitration_id, 0x100)
self.assertEqual(recv_msg.data, bytearray([1, 2, 3, 4, 5, 6, 7, 8]))


if __name__ == "__main__":
unittest.main()
Loading