From b6f4164454ff90d94be9c0bdb3ab7137c67ecb2c Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Thu, 11 Jun 2026 12:55:12 +0000 Subject: [PATCH] fix(core): drop video packets on a full UDP send buffer, don't fail the session MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UdpTransport sockets are non-blocking, so a momentarily-full kernel send buffer makes socket.send return WouldBlock (EAGAIN). submit_frame propagated that as a fatal error, tearing the whole punktfunk/1 session down — observed when attaching to an already-running source (a headless Steam session) that emits frames at full rate the instant capture connects: the first burst saturates the tx queue and the session dies before a single frame reaches the client. The data plane is lossy + Leopard-FEC-protected and runs infinite-GOP with RFI keyframes, so the real-time-correct response to a full tx queue is to DROP the packet (the next frame / FEC recovers) — exactly what the recv path already does for WouldBlock. Blocking would queue stale frames and add latency. Loopback/M1 paths are unaffected (LoopbackTransport never blocks; M1 tests stay green). Co-Authored-By: Claude Opus 4.8 (1M context) --- crates/punktfunk-core/src/transport/udp.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/crates/punktfunk-core/src/transport/udp.rs b/crates/punktfunk-core/src/transport/udp.rs index 057873a..74119f3 100644 --- a/crates/punktfunk-core/src/transport/udp.rs +++ b/crates/punktfunk-core/src/transport/udp.rs @@ -31,8 +31,17 @@ impl UdpTransport { impl Transport for UdpTransport { fn send(&self, packet: &[u8]) -> std::io::Result<()> { - self.socket.send(packet)?; - Ok(()) + match self.socket.send(packet) { + Ok(_) => Ok(()), + // The kernel UDP send buffer is momentarily full (a frame burst saturated the + // tx queue — common right after attaching to an already-running source that + // emits at full rate). Drop this packet rather than fail the whole stream: the + // data plane is lossy + FEC-protected and the next frame/RFI keyframe recovers, + // whereas blocking would queue stale frames and add latency, and erroring tears + // the session down. Mirrors the `recv` WouldBlock handling above. + Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => Ok(()), + Err(e) => Err(e), + } } fn recv(&self) -> std::io::Result>> {