{"record":{"id":"fd2eda6359c46568","repo":"tokio-rs/tokio","slug":"early-eof-fd2eda","errorCode":null,"errorMessage":"early eof","messagePattern":"early eof","errorType":"exception","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"tokio/src/io/util/read_exact.rs","lineNumber":44,"sourceCode":"\npin_project! {\n    /// Creates a future which will read exactly enough bytes to fill `buf`,\n    /// returning an error if EOF is hit sooner.\n    ///\n    /// On success the number of bytes is returned\n    #[derive(Debug)]\n    #[must_use = \"futures do nothing unless you `.await` or poll them\"]\n    pub struct ReadExact<'a, A: ?Sized> {\n        reader: &'a mut A,\n        buf: ReadBuf<'a>,\n        // Make this future `!Unpin` for compatibility with async trait methods.\n        #[pin]\n        _pin: PhantomPinned,\n    }\n}\n\nfn eof() -> io::Error {\n    io::Error::new(io::ErrorKind::UnexpectedEof, \"early eof\")\n}\n\nimpl<A> Future for ReadExact<'_, A>\nwhere\n    A: AsyncRead + Unpin + ?Sized,\n{\n    type Output = io::Result<usize>;\n\n    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<usize>> {\n        let me = self.project();\n\n        loop {\n            // if our buffer is empty, then we need to read some data to continue.\n            let rem = me.buf.remaining();\n            if rem != 0 {\n                match ready!(Pin::new(&mut *me.reader).poll_read(cx, me.buf)) {\n                    Ok(()) => {}\n                    Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,","sourceCodeStart":26,"sourceCodeEnd":62,"githubUrl":"https://github.com/tokio-rs/tokio/blob/7d0d729d8f03a0033d6752730d0fb5928962560e/tokio/src/io/util/read_exact.rs#L26-L62","documentation":"ReadExact::poll returns this io::ErrorKind::UnexpectedEof (built by the eof() helper) when the underlying AsyncRead signals EOF before the requested buffer is fully filled. read_exact guarantees either a full buffer or an error, so a short read is treated as failure. The number of bytes read so far is consumed and lost to the caller (only the error is returned).","triggerScenarios":"Calling AsyncReadExt::read_exact(&mut buf[..N]) (or read_buf_exact) on a reader that returns 0 bytes (EOF) before N bytes have been read. Also reachable via any future built on read_exact_internal such as reading fixed-length protocol frames.","commonSituations":"Reading a length-prefixed header where the peer sent fewer bytes then closed the connection; truncated file reads; a stream that hit EOF mid-record; network peer that sent a partial message then RST'd. Extremely common in protocol decoders.","solutions":["Treat UnexpectedEof as 'clean close / partial message' and decide whether the partial bytes are recoverable (use read instead of read_exact if short reads are acceptable).","Validate message completeness at the protocol layer before issuing a fixed-length read_exact.","If you need the partial bytes, switch to manual loop with read() and accumulate, tracking bytes read.","Confirm the peer is expected to keep the connection open for the full frame; add a keepalive/protocol-level handshake."],"exampleFix":"// before\nlet mut hdr = [0u8; 8];\nreader.read_exact(&mut hdr).await?; // fails on short msg\n\n// after\nlet mut hdr = [0u8; 8];\nmatch reader.read_exact(&mut hdr).await {\n    Ok(_) => Ok(hdr),\n    Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => {\n        // peer closed before full header; treat as end of stream\n        Err(anyhow::anyhow!(\"connection closed mid-frame\"))\n    }\n    Err(e) => Err(e.into()),\n}","handlingStrategy":"try-catch","validationCode":"// If short reads are acceptable, prefer read() over read_exact() and accumulate:\nlet mut filled = 0;\nwhile filled < buf.len() {\n    match reader.read(&mut buf[filled..]).await? {\n        0 => break, // EOF\n        n => filled += n,\n    }\n}","typeGuard":"fn is_unexpected_eof(e: &io::Error) -> bool {\n    e.kind() == io::ErrorKind::UnexpectedEof\n}","tryCatchPattern":"match reader.read_exact(&mut buf).await {\n    Ok(_) => Ok(buf),\n    Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => {\n        Err(anyhow::anyhow!(\"peer closed before frame complete\"))\n    }\n    Err(e) => Err(e.into()),\n}","preventionTips":["Use read (not read_exact) when partial data is acceptable.","Frame messages with an explicit length prefix so peers always send complete units.","Add a protocol-level heartbeat so unexpected EOFs are detected quickly.","Track bytes read manually if you need the partial payload after EOF."],"tags":["io","read-exact","eof","tokio"],"backgroundTag":null,"analyzedSha":"7d0d729d8f03a0033d6752730d0fb5928962560e","analyzedAt":"2026-08-11T17:46:45.378Z","contentChangedAt":"2026-08-11T17:46:45.378Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}