transport: bump QUIC idle timeout + add keep-alive for long builds

Field finding 2026-07-12 (clawverse cold on tank):

    Compiling claw-cli v0.1.0 (...)
    Finished `dev` profile ... in 45.08s
    cargo build finished in 45.135491979s
    Error: opening bidi stream for BlobPutStream
    Caused by: timed out

Cargo took 45s → QUIC's 30s idle timeout killed the connection between
the initial peer-lookup connect and the follow-up capture+upload path.
The RPC never got a chance to open its stream.

Fix: two belt-and-braces changes:
1. IDLE_TIMEOUT 30s → 600s. The timeout is there to detect crashed
   peers, not to enforce build pacing.
2. Client applies a `keep_alive_interval` of 15s so the connection
   stays warm across cargo runs even shorter than the idle window.

quinn's keep-alive fires from an internal runtime task, not the app
thread, so a fully-CPU-pinned cargo build doesn't suppress it.
This commit is contained in:
Omar Sobh
2026-07-12 05:53:21 -07:00
parent e70f5d74e0
commit c08f60a2aa
+25 -2
View File
@@ -44,7 +44,18 @@ pub const CLAWSTOR_RPC_ALPN: &[u8] = b"clawstor-rpc/1";
/// Idle timeout on connection — if no data for this long the connection
/// dies. Short enough to notice partitions, long enough to survive a
/// paused laptop.
const IDLE_TIMEOUT: Duration = Duration::from_secs(30);
/// Field finding 2026-07-12: raised from 30s to 10min because a long
/// `cargo build` between the initial connect and the follow-up upload
/// would silently kill the QUIC connection on `open_bi`. Cargo builds
/// on real workspaces routinely run for minutes; the idle timeout is
/// there to detect crashed peers, not to enforce interaction cadence.
const IDLE_TIMEOUT: Duration = Duration::from_secs(600);
/// Field finding 2026-07-12: keep the connection warm with a ping
/// every KEEP_ALIVE_INTERVAL — cheap belt-and-braces on top of the
/// larger idle window so cargo runs longer than the idle timeout
/// still stay dialed.
const KEEP_ALIVE_INTERVAL: Duration = Duration::from_secs(15);
/// Cap on any single RPC message payload. Ping/pong is tiny; other RPCs
/// stream larger payloads via streams-of-many-messages. Prevents an
@@ -441,7 +452,19 @@ impl QuicClient {
let client_crypto = build_client_crypto(&identity)?;
let quic_crypto = quinn::crypto::rustls::QuicClientConfig::try_from(client_crypto)
.context("wrapping rustls ClientConfig for quinn")?;
let client_config = ClientConfig::new(Arc::new(quic_crypto));
let mut client_config = ClientConfig::new(Arc::new(quic_crypto));
// Field finding 2026-07-12: apply the raised idle timeout + a
// keep-alive so a long cargo build between the initial connect
// and a follow-up upload doesn't kill the connection.
let mut transport = quinn::TransportConfig::default();
transport
.max_idle_timeout(Some(
VarInt::from_u64(IDLE_TIMEOUT.as_millis() as u64)
.expect("idle timeout fits u64")
.into(),
))
.keep_alive_interval(Some(KEEP_ALIVE_INTERVAL));
client_config.transport_config(Arc::new(transport));
let endpoint = Endpoint::client(bind_addr).context("binding quinn client endpoint")?;
Ok(Self {
endpoint,