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:
@@ -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;
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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()?;
|
||||
|
||||
Reference in New Issue
Block a user