{"record":{"id":"b96cac63c7dfd54b","repo":"herdrdev/herdr","slug":"timed-out-reading-stream-frame-body","errorCode":null,"errorMessage":"timed out reading stream frame body","messagePattern":"timed out reading stream frame body","errorType":"error_code","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"src/api/server/pane_graphics_stream.rs","lineNumber":470,"sourceCode":"                Some(idle_deadline),\n                Some(total_deadline),\n                \"timed out reading stream frame body\",\n            )?;\n            let remaining = len - data.len();\n            let read_len = remaining.min(chunk.len());\n            match stream.read(&mut chunk[..read_len]) {\n                Ok(0) if data.is_empty() => return Ok(None),\n                Ok(0) => {\n                    return Err(io::Error::new(\n                        io::ErrorKind::UnexpectedEof,\n                        \"stream ended mid-frame\",\n                    ));\n                }\n                Ok(n) => {\n                    wait.on_progress();\n                    let now = Instant::now();\n                    if now >= total_deadline {\n                        return Err(io::Error::new(\n                            io::ErrorKind::TimedOut,\n                            \"timed out reading stream frame body\",\n                        ));\n                    }\n                    data.extend_from_slice(&chunk[..n]);\n                    idle_deadline = now + idle_timeout;\n                }\n                Err(err) if read_should_retry(&err) => {\n                    wait.after_retry(Some(idle_deadline), Some(total_deadline));\n                }\n                Err(err) if is_connection_closed_error(&err) && data.is_empty() => return Ok(None),\n                Err(err) => return Err(err),\n            }\n        }\n\n        Ok(Some(data))\n    })\n}","sourceCodeStart":452,"sourceCodeEnd":488,"githubUrl":"https://github.com/herdrdev/herdr/blob/f457cff4f2648eee85d176f8a41861241d4e8428/src/api/server/pane_graphics_stream.rs#L452-L488","documentation":"While reading a declared frame body, read_exact tracks an absolute total deadline. Each successful read resets the idle deadline, but once 'now' passes total_deadline the read aborts with ErrorKind::TimedOut and 'timed out reading stream frame body'. So a body that trickles in (never idling long enough to hit the idle timeout) still cannot exceed the overall budget — this is the absolute cap for slow-drip bodies (exercised by trickled_graphics_body_obeys_absolute_deadline).","triggerScenarios":"A producer that sends body bytes slower than the total deadline allows — e.g. one byte per poll interval for a body larger than the budget covers; a stalled producer that already sent the header; total_timeout configured too small relative to frame size and producer speed.","commonSituations":"Large graphics frames (screenshots, big Kitty image payloads) over a slow pipe or loaded machine; debug-rate-limited producers in tests; deadline values tuned for headers reused for multi-MB bodies; backpressure from a slow consumer making the producer pause.","solutions":["Increase the total timeout budget proportional to the maximum expected frame size and slowest producer rate.","Split very large frames into multiple smaller frames so each fits the deadline.","Diagnose the producer's throughput (tracing, strace on write calls) — a healthy producer finishing a body should not approach the cap.","On this error, abort the frame cleanly and reopen/resynchronize the stream; partial body data must be discarded."],"exampleFix":"// before\nread_exact(&mut stream, len, idle, Duration::from_millis(500))?;\n\n// after\nread_exact(&mut stream, len, idle, Duration::from_secs(30))?;","handlingStrategy":"retry","validationCode":"null","typeGuard":null,"tryCatchPattern":"match read_exact(&mut stream, len, idle, total).await {\n    Err(e) if e.kind() == io::ErrorKind::TimedOut => {\n        // body budget exhausted: abort frame, discard partial bytes, reopen or renegotiate a smaller frame\n    }\n    other => other,\n}","preventionTips":["Scale the total deadline with expected frame size; don't reuse header deadlines for bodies.","Split large graphics payloads into multiple smaller frames.","Abort cleanly on timeout — never reuse partial body data."],"tags":["streaming","graphics","timeout","frame-body","rust"],"backgroundTag":"stream-read-timeout","analyzedSha":"f457cff4f2648eee85d176f8a41861241d4e8428","analyzedAt":"2026-08-28T15:41:09.197Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}