{"id":"103337a790ef41c4","repo":"hyperium/hyper","slug":"keep-alive-timed-out","errorCode":null,"errorMessage":"keep-alive timed out","messagePattern":"keep-alive timed out","errorType":"exception","errorClass":"hyper::Error","httpStatus":null,"severity":"error","filePath":"src/proto/h2/ping.rs","lineNumber":501,"sourceCode":"    fn maybe_timeout(&mut self, cx: &mut task::Context<'_>) -> Result<(), KeepAliveTimedOut> {\n        match self.state {\n            KeepAliveState::PingSent => {\n                if Pin::new(&mut self.sleep).poll(cx).is_pending() {\n                    return Ok(());\n                }\n                trace!(\"keep-alive timeout ({:?}) reached\", self.timeout);\n                Err(KeepAliveTimedOut)\n            }\n            KeepAliveState::Init | KeepAliveState::Scheduled(..) => Ok(()),\n        }\n    }\n}\n\n// ===== impl KeepAliveTimedOut =====\n\nimpl KeepAliveTimedOut {\n    pub(super) fn crate_error(self) -> crate::Error {\n        crate::Error::new(crate::error::Kind::Http2).with(self)\n    }\n}\n\nimpl fmt::Display for KeepAliveTimedOut {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        f.write_str(\"keep-alive timed out\")\n    }\n}\n\nimpl std::error::Error for KeepAliveTimedOut {\n    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {\n        Some(&crate::error::TimedOut)\n    }\n}\n","sourceCodeStart":483,"sourceCodeEnd":516,"githubUrl":"https://github.com/hyperium/hyper/blob/084473f728f9d07b3be5845475aa2f62ed9ff579/src/proto/h2/ping.rs#L483-L516","documentation":"Thrown by the HTTP/2 keep-alive machinery (KeepAliveTimedOut, src/proto/h2/ping.rs:507 Display, wrapped at :501 via crate_error). hyper sends an HTTP/2 PING after keep_alive_interval of inactivity; if no PONG (or any frame) arrives within keep_alive_timeout, the connection is considered dead and the error is surfaced. The underlying source is crate::error::TimedOut, Kind::Http2.","triggerScenarios":"An HTTP/2 peer that stops responding (no PONG, no data/ack frames) for longer than keep_alive_timeout after hyper sent a keep-alive PING. Configured on the client via Client::builder().http2_keep_alive_interval(...).keep_alive_timeout(...) (and while_idle), and on the server analogously.","commonSituations":"A backend that hangs (deadlock, GC pause, overloaded event loop); a network blackhole (path MTU, firewall dropping PING frames); keep_alive_timeout set too low for a slow/high-latency peer; peer/proxy that doesn't implement H2 PING acks; mobile clients that sleep without closing the connection.","solutions":["Increase keep_alive_timeout (and/or keep_alive_interval) to tolerate the peer's real RTT/processing time.","If the timeout is legitimately short, retry the request on a fresh connection (the old one is dead).","Verify network path: ensure H2 PING frames aren't being dropped by a middlebox/proxy.","Confirm the peer (server or client) and any intermediary support H2 keep-alive PINGs.","Disable keep-alive probing (http2_keep_alive_interval = None) only if an external liveness check exists."],"exampleFix":"// before: aggressive keep-alive timeout\nlet client = hyper::Client::builder()\n    .http2_keep_alive_interval(Some(Duration::from_secs(5)))\n    .keep_alive_timeout(Duration::from_millis(500)) // too tight -> error 35\n    .build_http::<hyper::Body>();\n\n// after: tolerate slow peers and retry on timeout\nlet client = hyper::Client::builder()\n    .http2_keep_alive_interval(Some(Duration::from_secs(15)))\n    .keep_alive_timeout(Duration::from_secs(20))\n    .build_http::<hyper::Body>();","handlingStrategy":"retry","validationCode":"// Configure keep-alive to match the slowest legitimate peer RTT/processing.\nuse std::time::Duration;\nlet client = hyper::Client::builder()\n    .http2_keep_alive_interval(Some(Duration::from_secs(15)))\n    .http2_keep_alive_timeout(Duration::from_secs(20)) // >= worst-case RTT\n    .http2_keep_alive_while_idle(true)\n    .build_http::<hyper::Body>();","typeGuard":null,"tryCatchPattern":"// Distinguish keep-alive timeout from other h2 errors and retry idempotent calls.\nasync fn fetch_retry(client: &hyper::Client<...>, req: hyper::Request<hyper::Body>) -> Result<_, hyper::Error> {\n    for attempt in 0..3 {\n        match client.request(req.clone()).await {\n            Ok(resp) => return Ok(resp),\n            Err(e) => {\n                let is_timeout = e.source()\n                    .and_then(|s| s.downcast_ref::<std::io::Error>())\n                    .map(|io| matches!(io.kind(), std::io::ErrorKind::TimedOut))\n                    .unwrap_or(false)\n                    || e.to_string().contains(\"keep-alive timed out\");\n                if is_timeout && attempt < 2 {\n                    tracing::warn!(attempt, \"h2 keep-alive timeout; retrying on new conn\");\n                    continue;\n                }\n                return Err(e);\n            }\n        }\n    }\n    unreachable!()\n}","preventionTips":["Set http2_keep_alive_timeout above the peer's worst-case RTT + processing time.","Retry idempotent requests on a fresh connection when keep-alive times out (the old conn is dead).","Verify middleboxes/proxies in the path actually forward H2 PING frames.","For battery-constrained clients, consider http2_keep_alive_while_idle=false and rely on app-level liveness."],"tags":["http","http2","keep-alive","ping","timeout","network","hyper","rust"],"analyzedSha":"084473f728f9d07b3be5845475aa2f62ed9ff579","analyzedAt":"2026-08-06T01:20:18.522Z","schemaVersion":2}