Apply a 4 MB receive buffer on system and proxy UDP sockets to reduce kernel RcvbufErrors under OBP load; size is configurable via GLOBAL.UDP_RCVBUF.pull/34/head
parent
2e4026f9d7
commit
ad9ab2a024
@ -0,0 +1,60 @@
|
||||
# ADN DMR Peer Server - UDP receive buffer sizing
|
||||
# Copyright (C) 2026 Rodrigo Pérez, CE5RPY <ce5rpy@qmd.cl>
|
||||
#
|
||||
###############################################################################
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
###############################################################################
|
||||
|
||||
"""Raise SO_RCVBUF on voice UDP listeners to avoid kernel RcvbufErrors under load."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import socket
|
||||
from typing import Any
|
||||
|
||||
DEFAULT_UDP_RCVBUF = 4 * 1024 * 1024
|
||||
|
||||
|
||||
def udp_rcvbuf_bytes(config: dict[str, Any] | None) -> int:
|
||||
if not config:
|
||||
return DEFAULT_UDP_RCVBUF
|
||||
raw = config.get("GLOBAL", {}).get("UDP_RCVBUF", DEFAULT_UDP_RCVBUF)
|
||||
if isinstance(raw, bool) or not isinstance(raw, int) or raw <= 0:
|
||||
return DEFAULT_UDP_RCVBUF
|
||||
return raw
|
||||
|
||||
|
||||
def apply_udp_rcvbuf(
|
||||
sock: socket.socket,
|
||||
requested: int,
|
||||
*,
|
||||
label: str,
|
||||
logger: logging.Logger,
|
||||
) -> None:
|
||||
try:
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, requested)
|
||||
except OSError as exc:
|
||||
logger.warning("(%s) UDP RX buffer not raised (requested %s): %s", label, requested, exc)
|
||||
return
|
||||
try:
|
||||
effective = sock.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF)
|
||||
except OSError as exc:
|
||||
logger.warning("(%s) UDP RX buffer set but getsockopt failed: %s", label, exc)
|
||||
return
|
||||
logger.info("(%s) UDP RX buffer raised to %s bytes (requested %s)", label, effective, requested)
|
||||
|
||||
|
||||
__all__ = ["DEFAULT_UDP_RCVBUF", "apply_udp_rcvbuf", "udp_rcvbuf_bytes"]
|
||||
@ -0,0 +1,48 @@
|
||||
"""Tests for UDP receive buffer helper."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import socket
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from adn_server.infrastructure.udp_rcvbuf import (
|
||||
DEFAULT_UDP_RCVBUF,
|
||||
apply_udp_rcvbuf,
|
||||
udp_rcvbuf_bytes,
|
||||
)
|
||||
|
||||
|
||||
def test_udp_rcvbuf_bytes_default() -> None:
|
||||
assert udp_rcvbuf_bytes(None) == DEFAULT_UDP_RCVBUF
|
||||
assert udp_rcvbuf_bytes({}) == DEFAULT_UDP_RCVBUF
|
||||
assert udp_rcvbuf_bytes({"GLOBAL": {}}) == DEFAULT_UDP_RCVBUF
|
||||
|
||||
|
||||
def test_udp_rcvbuf_bytes_from_config() -> None:
|
||||
assert udp_rcvbuf_bytes({"GLOBAL": {"UDP_RCVBUF": 2097152}}) == 2097152
|
||||
|
||||
|
||||
def test_udp_rcvbuf_bytes_rejects_invalid() -> None:
|
||||
assert udp_rcvbuf_bytes({"GLOBAL": {"UDP_RCVBUF": 0}}) == DEFAULT_UDP_RCVBUF
|
||||
assert udp_rcvbuf_bytes({"GLOBAL": {"UDP_RCVBUF": -1}}) == DEFAULT_UDP_RCVBUF
|
||||
assert udp_rcvbuf_bytes({"GLOBAL": {"UDP_RCVBUF": "big"}}) == DEFAULT_UDP_RCVBUF
|
||||
assert udp_rcvbuf_bytes({"GLOBAL": {"UDP_RCVBUF": True}}) == DEFAULT_UDP_RCVBUF
|
||||
|
||||
|
||||
def test_apply_udp_rcvbuf_sets_socket_buffer() -> None:
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
try:
|
||||
requested = 2 * 1024 * 1024
|
||||
log = MagicMock(spec=logging.Logger)
|
||||
apply_udp_rcvbuf(sock, requested, label="TEST", logger=log)
|
||||
effective = sock.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF)
|
||||
assert effective >= requested
|
||||
log.info.assert_called_once()
|
||||
args = log.info.call_args[0]
|
||||
assert args[0] == "(%s) UDP RX buffer raised to %s bytes (requested %s)"
|
||||
assert args[1] == "TEST"
|
||||
assert args[2] == effective
|
||||
assert args[3] == requested
|
||||
finally:
|
||||
sock.close()
|
||||
Loading…
Reference in new issue