{"record":{"id":"d6da404318e16238","repo":"EpicGames/lore","slug":"file-ended-before-the-requested-read-length-psync","errorCode":null,"errorMessage":"file ended before the requested read length","messagePattern":"file ended before the requested read length","errorType":"exception","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"lore-io/src/psync.rs","lineNumber":83,"sourceCode":"    /// job already owns the buffer and the thread, so returning to the caller between syscalls\n    /// would buy nothing and pay two handoffs. This is the shape the whole-file read below and\n    /// the file scan this backend replaces both use.\n    pub(crate) async fn read_exact_at(\n        &self,\n        file: Arc<File>,\n        len: usize,\n        offset: u64,\n    ) -> std::io::Result<Bytes> {\n        SyscallPool::global()\n            .submit(move || {\n                // SAFETY: every byte up to `len` is filled before returning, and a short read\n                // returns an error rather than the buffer.\n                let mut buffer = unsafe { crate::buffer::uninit_buffer(len) };\n                let mut done = 0;\n                while done < len {\n                    let read = read_at_impl(&file, &mut buffer[done..len], offset + done as u64)?;\n                    if read == 0 {\n                        return Err(std::io::Error::new(\n                            std::io::ErrorKind::UnexpectedEof,\n                            \"file ended before the requested read length\",\n                        ));\n                    }\n                    done += read;\n                }\n                Ok(buffer.freeze())\n            })\n            .await\n    }\n\n    /// Writes all `len` bytes, looping inside one dispatch. See [`Self::read_exact_at`].\n    pub(crate) async fn write_all_at<B: StableBuf>(\n        &self,\n        file: Arc<File>,\n        buffer: B,\n        len: usize,\n        offset: u64,","sourceCodeStart":65,"sourceCodeEnd":101,"githubUrl":"https://github.com/EpicGames/lore/blob/074eb0b0d1194c997d7cf28b55519e3e197b3e23/lore-io/src/psync.rs#L65-L101","documentation":"read_exact_at reached end-of-file before filling the requested number of bytes: a read at `offset` returned 0 bytes, meaning the file ended (or the offset is past EOF). The library treats a short read as an error (ErrorKind::UnexpectedEof) so callers always get the full requested buffer.","triggerScenarios":"Calling read_exact_at with (offset + len) beyond the current file size; the file was truncated by another process mid-read; a sparse/partial write left the tail of the file shorter than expected; retry loop consumed reads until a 0-byte read.","commonSituations":"Reading a fixed-size header/footer from a truncated or partially-written file; concurrent writer truncated the file between a size check and the read; off-by-one in an offset calculation; reading from a log still being appended by another process.","solutions":["Verify offset + len <= file metadata len() before calling read_exact_at.","Re-check the file size and re-read after the writer finishes (use file locking or wait for a completion marker).","Fix offset arithmetic; ensure offsets are in bytes and point at data actually written.","Fall back to reading whatever is available with a plain read_at if a short read is acceptable."],"exampleFix":"// before\nfile.read_exact_at(&mut header, 0).await?; // UnexpectedEof if file < header.len()\n// after\nlet len = file.metadata().await?.len();\nif (header.len() as u64) > len {\n    return Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, \"file too short\"));\n}\nfile.read_exact_at(&mut header, 0).await?;","handlingStrategy":"validation","validationCode":"// Rust\nlet len = file.metadata().await?.len();\nif offset + buf.len() as u64 > len {\n    return Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, \"read exceeds file size\"));\n}","typeGuard":null,"tryCatchPattern":"// Rust\nmatch file.read_exact_at(&mut buf, offset).await {\n    Ok(()) => {}\n    Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => { /* re-stat, reopen, or treat as truncated file */ }\n    Err(e) => return Err(e.into()),\n}","preventionTips":["Verify offset + len <= file size before exact reads","Re-stat after concurrent writers finish, or use file locking","Prefer length-prefixed formats over assumed fixed sizes","Treat UnexpectedEof as 'file changed' and retry with a fresh handle"],"tags":["io","filesystem","read","eof"],"backgroundTag":"file-read-failed","analyzedSha":"074eb0b0d1194c997d7cf28b55519e3e197b3e23","analyzedAt":"2026-09-13T09:00:57.509Z","contentChangedAt":"2026-09-13T09:00:57.509Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}