Skip to content

feat(network): VITA-49 RX buffer — raise default to 4 MiB + operator-adjustable slider (#3810)#3811

Merged
ten9876 merged 2 commits into
mainfrom
fix/panadapter-stream-rcvbuf
Jun 26, 2026
Merged

feat(network): VITA-49 RX buffer — raise default to 4 MiB + operator-adjustable slider (#3810)#3811
ten9876 merged 2 commits into
mainfrom
fix/panadapter-stream-rcvbuf

Conversation

@ten9876

@ten9876 ten9876 commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

Summary

Closes #3810. Raises SO_RCVBUF on the PanadapterStream VITA-49 UDP socket to 4 MiB, fixing the "network stats drop at various times" symptom.

Root cause

The stream socket never set SO_RCVBUF, so it used the OS default (~208 KB on Linux). VITA-49 bursts (panadapter FFT + waterfall tiles + audio + meters) — or a brief worker-thread drain stall under host load — overflow that buffer; the kernel drops the excess. Those drops surface as VITA-49 sequence gapsRadioModel::evaluateNetworkQuality() reads them as packet loss → degrades the net state → the adaptive throttle caps the radio's pan FPS (down to 4 fps) → visible stat drop, held 5 s (min-dwell), then lifts → re-trips on the next burst.

Live evidence: a running session's stream socket showed 370 receive-buffer overflow drops (ss -md370) against an rb212992 (~208 KB) buffer, arriving in a burst, not a steady leak.

Change

  • New applyReceiveBufferSize() helper, called after a successful bind on all three paths (LAN start, ephemeral rebindToEphemeralPort, startWan).
  • Requests 4 MiB and logs the granted size — the kernel caps the request at net.core.rmem_max, so an undersized rmem_max is now visible in the logs (granted=… (capped by net.core.rmem_max …)) instead of silently limiting the buffer.
  • Cross-platform (QAbstractSocket::ReceiveBufferSizeSocketOption). +36 lines, one subsystem.

Test plan

  • Builds clean
  • On a live FLEX session: log shows granted= near 4 MiB; the stream socket's ss -m drop counter stops climbing across bursts/band-changes
  • Network-stats panel no longer shows the periodic FPS-cap dips on a healthy LAN
  • WAN path still binds/streams (the startWan call site)

Relation to #3797

The pegged GUI thread (#3797) makes overflow more likely (it can stall the socket drain during bursts), so the two compound — but the buffer is the directly fixable root for this symptom; #3797 is the separate render-side bottleneck.

🤖 Generated with Claude Code

…drops

Closes #3810. The PanadapterStream UDP socket never raised SO_RCVBUF, so it used
the OS default (~208 KB on Linux). VITA-49 bursts (panadapter FFT + waterfall +
audio + meters) — or a brief worker-thread drain stall under host load —
overflow that buffer; the kernel drops the excess, which surfaces as VITA-49
sequence gaps. RadioModel::evaluateNetworkQuality() reads those as packet loss,
degrades the net state, and the adaptive throttle caps the radio's pan FPS
(down to 4 fps) — the visible 'network stats drop at various times'.

Live evidence: a running session's stream socket showed 370 receive-buffer
overflow drops (ss -m d370) against an rb212992 (~208 KB) buffer, arriving in a
burst rather than a steady leak.

Fix: request a 4 MiB SO_RCVBUF on the bound socket (all three bind paths: LAN,
ephemeral rebind, WAN) via a shared applyReceiveBufferSize() helper, and log the
granted size — the kernel caps the request at net.core.rmem_max, so a too-small
rmem_max is now visible in the logs instead of silently limiting us. Absorbs
normal bursts so they no longer present as false sequence-loss and stop tripping
the throttle. Cross-platform (ReceiveBufferSizeSocketOption).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ten9876 ten9876 requested a review from a team as a code owner June 25, 2026 13:54

@aethersdr-agent aethersdr-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this, @ten9876 — clean, tightly-scoped fix with a well-argued root cause. The change is solid and I'd be comfortable seeing it merge. A few observations:

What's good

  • applyReceiveBufferSize() is correctly called after a successful bind() and before the UDP registration datagram on all three paths (start, rebindToEphemeralPort, startWan), so the buffer is enlarged before the radio can start sending. Good that you didn't miss the WAN site.
  • m_socket null-guard up front, QAbstractSocket::ReceiveBufferSizeSocketOption for cross-platform behavior, and logging the granted size to expose an undersized net.core.rmem_max — all the right instincts at this system boundary.
  • No conventions concerns (no settings, no ownership/RAII surface), and it stays within the two files the PR claims.

One thing worth a note (cosmetic, not blocking):
On Linux the kernel stores SO_RCVBUF as double the requested value for its own bookkeeping, and getsockopt(SO_RCVBUF) (which Qt's socketOption() reads) returns that doubled figure. So on an uncapped host, requesting 4 MiB will log granted=8388608 — roughly 2× what you asked for — which can read as surprising. The capped-detection logic still works correctly (a 208 KB rmem_max doubles to ~416 KB, still < 4 MiB), so this is purely a log-readability point. If you want the log to be unambiguous you could note the doubling, e.g. append (Linux reports 2×) or halve the displayed value on Linux — but it's entirely optional.

Minor, optional: the 4 MiB target is a hardcoded constant. Fine as a default for this fix; just flagging that if a future host needs tuning it'd require a rebuild rather than a setting. Not worth changing now.

The test plan still has the live-FLEX verification boxes unchecked — worth confirming the granted= log lands near 4 MiB and the ss -m drop counter stops climbing on real hardware before merge, since that's the actual proof the buffer size resolves the symptom. Nice work.


🤖 aethersdr-agent · cost: $7.1913 · model: claude-opus-4-8

…der)

Extends the SO_RCVBUF fix into a user-tunable control. Radio Setup → Advanced
gets a snap-to-preset slider (256K / 512K / 1M / 2M / 4M, default 4M) for the
VITA-49 stream socket receive buffer, with a live 'granted:' readout so the
kernel's net.core.rmem_max clamp is visible rather than silently limiting the
request.

- NetworkSettings (new): persists the request as nested JSON under the single
  AppSettings 'Network' key (Principle V — no new flat keys).
- PanadapterStream: seeds m_desiredRcvBufBytes from NetworkSettings at init;
  applyReceiveBufferSize() requests it on every bind, records the granted size,
  and emits receiveBufferApplied(requested, granted). New Q_INVOKABLE
  setReceiveBufferSizeBytes() re-applies live on the network worker thread.
- RadioSetupDialog: the slider persists the choice and re-applies live via
  QMetaObject::invokeMethod onto the worker thread (mirrors the existing PLC
  toggle), and the granted label updates from receiveBufferApplied (queued).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ten9876 ten9876 changed the title perf(network): raise VITA-49 stream SO_RCVBUF to 4 MiB to stop burst drops (#3810) feat(network): VITA-49 RX buffer — raise default to 4 MiB + operator-adjustable slider (#3810) Jun 25, 2026
@ten9876

ten9876 commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator Author

Update: the buffer is now operator-adjustable

Per maintainer request, extended this beyond the fixed 4 MiB into a user control.

Radio Setup → Advanced → "VITA-49 RX buffer" — a snap-to-preset slider (256K / 512K / 1M / 2M / 4M, default 4M) with a live granted: readout beneath it. Because the kernel clamps the request at net.core.rmem_max, the granted label shows what the system actually gave (and appends (capped by net.core.rmem_max) when clamped) — so the slider never claims more than is real.

How it's wired

  • NetworkSettings (new, src/core/): persists the request as nested JSON under the single AppSettings Network key — Principle V, no new flat keys.
  • PanadapterStream: seeds the request from NetworkSettings at init(); applyReceiveBufferSize() requests it on every bind, records the granted size, and emits receiveBufferApplied(requested, granted). New Q_INVOKABLE setReceiveBufferSizeBytes() re-applies live (no reconnect needed).
  • RadioSetupDialog: the slider persists the choice and re-applies live via QMetaObject::invokeMethod onto the network worker thread (mirrors the existing PLC-toggle pattern); the granted label updates from the queued receiveBufferApplied signal.

Latency note (raised in review)

SO_RCVBUF is a capacity ceiling, not a fill target — it adds no steady-state delay (the worker drains immediately; occupancy ≈ 0) and doesn't touch TX (separate socket). It only changes drop into deliver-a-few-ms-later during a burst. The slider lets a latency-sensitive operator dial it down if desired.

Test plan (updated)

  • Builds clean
  • Slider persists across restart; moving it logs a new granted= and updates the label live (no reconnect)
  • On a low-rmem_max box, the label shows (capped …) at higher presets
  • Drop counter stops climbing at 4M on a live FLEX session; dialing to 256K reproduces drops (confirms the mechanism end-to-end)
  • All three bind paths (LAN / rebind / WAN) still apply the buffer

@rfoust

rfoust commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

Should there be an “auto” mode that self adjusts based on observed metrics over some timeframe?

@ten9876 ten9876 merged commit 2ccb4ca into main Jun 26, 2026
6 checks passed
@ten9876 ten9876 deleted the fix/panadapter-stream-rcvbuf branch June 26, 2026 13:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

perf(network): VITA-49 stream socket uses ~208KB default SO_RCVBUF → burst drops trip the adaptive throttle (network stats drop)

2 participants