Skip to content

Commit

Permalink
Avoid ENOBUFS OSErrors by shrinking the size of SNDBUF when appropria…
Browse files Browse the repository at this point in the history
…te. (#234)
  • Loading branch information
wiboticalex authored Jul 7, 2022
1 parent 2cbeb12 commit 62690b3
Show file tree
Hide file tree
Showing 3 changed files with 31 additions and 3 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ v1.9
- Cyphal/CAN: Add support for GS USB adapter support via PythonCAN
(`#212 <https://github.com/OpenCyphal/pycyphal/pull/212>`_).

- Cyphal/CAN: Adjust SocketCAN socket behavior to avoid ENOBUFS
(`#234 <https://github.com/OpenCyphal/pycyphal/pull/234>`_).

v1.8
----

Expand Down
2 changes: 1 addition & 1 deletion pycyphal/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.9.0.beta1
1.9.0.beta2
29 changes: 27 additions & 2 deletions pycyphal/transport/can/media/socketcan/_socketcan.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import warnings
import threading
import contextlib
import pathlib
import pycyphal.transport
from pycyphal.transport import Timestamp
from pycyphal.transport.can.media import Media, Envelope, FilterConfiguration, FrameFormat
Expand Down Expand Up @@ -79,7 +80,7 @@ def __init__(self, iface_name: str, mtu: int, loop: typing.Optional[asyncio.Abst
)
self._native_frame_size = _FRAME_HEADER_STRUCT.size + self._native_frame_data_capacity

self._sock = _make_socket(iface_name, can_fd=self._is_fd)
self._sock = _make_socket(iface_name, can_fd=self._is_fd, native_frame_size=self._native_frame_size)
self._ctl_main, self._ctl_worker = socket.socketpair() # This is used for controlling the worker thread.
self._closed = False
self._maybe_thread: typing.Optional[threading.Thread] = None
Expand Down Expand Up @@ -309,6 +310,7 @@ class _NativeFrameDataCapacity(enum.IntEnum):

# From the Linux kernel; not exposed via the Python's socket module
_SO_TIMESTAMP = 29
_SO_SNDBUF = 7

_CANFD_BRS = 1

Expand All @@ -318,12 +320,35 @@ class _NativeFrameDataCapacity(enum.IntEnum):

_CAN_EFF_MASK = 0x1FFFFFFF

# approximate sk_buffer kernel struct overhead.
# A lower estimate over higher estimate is preferred since _SO_SNDBUF will enforce
# a minimum value, and blocking behavior will not work if this is too high.
_SKB_OVERHEAD = 444

def _make_socket(iface_name: str, can_fd: bool) -> socket.socket:

def _get_tx_queue_len(iface_name: str) -> int:
try:
sysfs_net = pathlib.Path("/sys/class/net/")
sysfs_tx_queue_len = sysfs_net / iface_name / "tx_queue_len"
return int(sysfs_tx_queue_len.read_text())
except FileNotFoundError as e:
raise FileNotFoundError("tx_queue_len sysfs location not found") from e


def _make_socket(iface_name: str, can_fd: bool, native_frame_size: int) -> socket.socket:
s = socket.socket(socket.PF_CAN, socket.SOCK_RAW, socket.CAN_RAW) # type: ignore
try:
s.bind((iface_name,))
s.setsockopt(socket.SOL_SOCKET, _SO_TIMESTAMP, 1) # timestamping
default_sndbuf_size = s.getsockopt(socket.SOL_SOCKET, _SO_SNDBUF)
blocking_sndbuf_size = (native_frame_size + _SKB_OVERHEAD) * _get_tx_queue_len(iface_name)

# Allow CAN sockets to block when full similar to how Ethernet sockets do.
# Avoids ENOBUFS errors on TX when queues are full in most cases.
# More info:
# - https://github.com/OpenCyphal/pycyphal/issues/233
# - "SocketCAN and queueing disciplines: Final Report", Sojka et al, 2012
s.setsockopt(socket.SOL_SOCKET, _SO_SNDBUF, min(blocking_sndbuf_size, default_sndbuf_size) // 2)
if can_fd:
s.setsockopt(socket.SOL_CAN_RAW, socket.CAN_RAW_FD_FRAMES, 1) # type: ignore

Expand Down

0 comments on commit 62690b3

Please sign in to comment.