cloudflare/quiche · warning · io::Error
TimedOut
TimedOut
Error message
connection {scid:?} timed out What it means
When tokio-quiche's connection router accepts a new incoming connection (on_incoming), a connection that has already fully closed (never completed its handshake) is reported as TimedOut with the connection's SCID. A closed-before-handshake connection is treated as a handshake timeout.
Solutions
- Inspect server logs for the SCID to correlate with the offending client
- Check client QUIC version and initial packet validity
- If this happens en masse, review timeouts (max_idle_timeout) and any middleboxes dropping handshake packets
- Treat occasional occurrences as normal Internet noise
Defensive patterns
Strategy: try-catch
Try / catch
match router.handle_initials(...).await {
Err(e) if e.kind() == io::ErrorKind::TimedOut => {
log::warn!("handshake closed before completing: {e}"); // expected client churn
}
other => other?,
} Prevention
- Monitor the rate of these errors; occasional ones are normal Internet traffic
- Tune handshake timeouts and max_idle_timeout for slow clients
- Log the SCID to correlate with client-side traces
When it happens
Trigger: An incoming QUIC initial that gets closed before the handshake completes — conn.is_closed() is true at on_incoming time, called from handle_initials.
Common situations: Clients sending malformed or stale initials; idle/dead clients probing the server; version-negotiation failures; load balancer health checks that open and abort connections.
Understand the failure class
Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- initial send failed
- error closing conn
- Can't use RPK when compiled without rpk feature
- initial send failed
- datagram extension not enabled by peer
AI-assisted analysis of cloudflare/quiche@9f96daa2c2 (2026-09-08).
Data as JSON: /api/errors/871b9e2753fa1b10.
Report an issue: GitHub.
Appendix: source
Thrown at tokio-quiche/src/quic/router/connector.rs:191
self.timeout_queue.remove(&key);
}
let scid = conn.source_id();
if conn.is_established() {
log::debug!("QUIC connection established"; "scid" => ?scid);
Ok(Some(NewConnection {
conn,
pending_cid: None,
initial_pkt: None,
cid_generator: None,
handshake_start_time,
}))
} else if conn.is_closed() {
let scid = conn.source_id();
log::error!("QUIC connection closed on_incoming"; "scid" => ?scid);
Err(io::Error::new(
io::ErrorKind::TimedOut,
format!("connection {scid:?} timed out"),
))
} else {
self.set_connection_to_pending(conn).map(|()| None)
}
}
/// [`ClientConnector::on_timeout`] runs [`quiche::Connection::on_timeout`]
/// for a pending connection. If the connection is closed, this sends an
/// error upstream.
fn on_timeout(&mut self, scid: ConnectionId<'static>) -> io::Result<()> {
log::debug!("connection timedout"; "scid" => ?scid);
let Some(mut pending) =
self.connection.take_if_pending_and_id_matches(&scid)
else {
log::debug!("timedout connection missing from pending map"; "scid" => ?scid);View on GitHub (pinned to 9f96daa2c2)