perf(peer): tune QUIC flow control for LAN downloads

Raise the s2n-quic connection and stream data windows on both the client and
server, increase the max send buffer, and use BBR with a larger initial
congestion window. The download path was already able to pipeline multiple chunk
streams, but those streams still shared small default connection-level budgets
that limited sustained LAN throughput.

The tuning keeps one current wire protocol and does not add fallback behavior.
It is deliberately centralized in the peer networking module so later transport
changes can use the same limits on both sides of the connection.

S37 single-source throughput:
- Before: 733.22 MiB/s, 6150.72 Mbit/s, 2.793s
- After: 824.94 MiB/s, 6920.09 Mbit/s, 2.483s
- Delta: +91.72 MiB/s, +769.37 Mbit/s, about +12.5%

Test Plan:
- just fmt
- python3 crates/lanspread-peer-cli/scripts/run_extended_scenarios.py S37 --build-image

Refs: local LAN download performance investigation on 2026-05-20.
Depends-on: 14e772c5c71a (peer-cli S37 throughput measurement).
This commit is contained in:
2026-05-20 08:27:49 +02:00
parent 8a9f420a06
commit 5b689ec5f4
3 changed files with 46 additions and 5 deletions
+12
View File
@@ -23,6 +23,18 @@ pub const PEER_DOWNLOAD_STREAM_WINDOW: usize = 4;
/// Maximum number of retry attempts for failed chunk downloads.
pub const MAX_RETRY_COUNT: usize = 3;
/// QUIC connection-level receive window for bulk LAN transfers (64 MiB).
pub const QUIC_CONNECTION_DATA_WINDOW: u64 = 64 * 1024 * 1024;
/// QUIC per-stream receive window for bulk LAN transfers (32 MiB).
pub const QUIC_STREAM_DATA_WINDOW: u64 = 32 * 1024 * 1024;
/// Maximum queued send data per QUIC stream (32 MiB).
pub const QUIC_MAX_SEND_BUFFER_SIZE: u32 = 32 * 1024 * 1024;
/// Initial congestion window for LAN-oriented BBR transfers (1 MiB).
pub const QUIC_INITIAL_CONGESTION_WINDOW: u32 = 1024 * 1024;
/// Fallback interval for reconciling missed filesystem watcher events (seconds).
pub const LOCAL_GAME_FALLBACK_SCAN_SECS: u64 = 300;
+30 -3
View File
@@ -10,19 +10,46 @@ use futures::{SinkExt, StreamExt};
use if_addrs::{IfAddr, Interface, get_if_addrs};
use lanspread_db::db::GameFileDescription;
use lanspread_proto::{Hello, HelloAck, LibraryDelta, Message, Request, Response};
use s2n_quic::{Client as QuicClient, Connection, client::Connect, provider::limits::Limits};
use s2n_quic::{
Client as QuicClient,
Connection,
client::Connect,
provider::{congestion_controller, limits::Limits},
};
use tokio_util::codec::{FramedRead, FramedWrite, LengthDelimitedCodec};
use crate::config::CERT_PEM;
use crate::config::{
CERT_PEM,
QUIC_CONNECTION_DATA_WINDOW,
QUIC_INITIAL_CONGESTION_WINDOW,
QUIC_MAX_SEND_BUFFER_SIZE,
QUIC_STREAM_DATA_WINDOW,
};
pub(crate) fn quic_limits() -> eyre::Result<Limits> {
Ok(Limits::default()
.with_data_window(QUIC_CONNECTION_DATA_WINDOW)?
.with_bidirectional_local_data_window(QUIC_STREAM_DATA_WINDOW)?
.with_bidirectional_remote_data_window(QUIC_STREAM_DATA_WINDOW)?
.with_unidirectional_data_window(QUIC_STREAM_DATA_WINDOW)?
.with_max_send_buffer_size(QUIC_MAX_SEND_BUFFER_SIZE)?)
}
pub(crate) fn quic_congestion_controller() -> congestion_controller::Bbr {
congestion_controller::bbr::Builder::default()
.with_initial_congestion_window(QUIC_INITIAL_CONGESTION_WINDOW)
.build()
}
/// Establishes a QUIC connection to a peer.
pub async fn connect_to_peer(addr: SocketAddr) -> eyre::Result<Connection> {
let limits = Limits::default().with_max_handshake_duration(Duration::from_secs(3))?;
let limits = quic_limits()?.with_max_handshake_duration(Duration::from_secs(3))?;
let client = QuicClient::builder()
.with_tls(CERT_PEM)?
.with_io("0.0.0.0:0")?
.with_limits(limits)?
.with_congestion_controller(quic_congestion_controller())?
.start()?;
let conn = Connect::new(addr).with_server_name("localhost");
+4 -2
View File
@@ -2,7 +2,7 @@
use std::{net::SocketAddr, time::Duration};
use s2n_quic::{Connection, Server, provider::limits::Limits};
use s2n_quic::{Connection, Server};
use tokio::sync::mpsc::UnboundedSender;
use crate::{
@@ -10,6 +10,7 @@ use crate::{
config::{CERT_PEM, KEY_PEM},
context::PeerCtx,
events,
network::{quic_congestion_controller, quic_limits},
services::{
advertise::{monitor_mdns_events, start_mdns_advertiser},
stream::handle_peer_stream,
@@ -22,7 +23,7 @@ pub async fn run_server_component(
ctx: PeerCtx,
tx_notify_ui: UnboundedSender<PeerEvent>,
) -> eyre::Result<()> {
let limits = Limits::default()
let limits = quic_limits()?
.with_max_handshake_duration(Duration::from_secs(3))?
.with_max_idle_timeout(Duration::from_secs(3))?;
@@ -30,6 +31,7 @@ pub async fn run_server_component(
.with_tls((CERT_PEM, KEY_PEM))?
.with_io(addr)?
.with_limits(limits)?
.with_congestion_controller(quic_congestion_controller())?
.start()?;
let server_addr = server.local_addr()?;