EpicGames/lore · error · io::Error (UnexpectedEof)
file ended before the requested read length
Error message
file ended before the requested read length
What it means
The io_uring read_exact_at loop treats a completed read that returns 0 bytes (Progress::Bytes(0)) as end-of-file and fails with ErrorKind::UnexpectedEof. Like the other backends, exact reads must fill the whole buffer or error, so callers never see a partially-filled buffer on success.
Solutions
- Check file size >= offset + len before issuing the read.
- Reopen the file and retry after the writer/truncator settles.
- Fix offset/length calculations for the record layout.
- Use a length-prefixed format or stat the file instead of assuming fixed sizes.
Example fix
// before
file.read_exact_at(&mut record, offset).await?; // UnexpectedEof near EOF
// after
let len = file.metadata().await?.len();
if offset + record.len() as u64 > len {
return Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "record past EOF"));
}
file.read_exact_at(&mut record, offset).await?; Defensive patterns
Strategy: validation
Validate before calling
// Rust
let len = file.metadata().await?.len();
if offset + buf.len() as u64 > len {
return Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "read exceeds file size"));
} Try / catch
// Rust
match file.read_exact_at(&mut buf, offset).await {
Ok(()) => {}
Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => { /* re-stat / reopen / bounded read */ }
Err(e) => return Err(e.into()),
} Prevention
- Validate offsets against file length before io_uring exact reads
- Reopen the handle after external truncation
- Use fixed-size records only with files whose size you control
- Fall back to plain read_at when a short read is acceptable
When it happens
Trigger: Calling read_exact_at with offset+len beyond the file's current size; the file was truncated between submission and completion; retries (Progress::Interrupted) eventually hit a 0-byte read at the tail; reading a sparse region past EOF.
Common situations: Reading fixed-size records from a truncated file; another process shrank the file during async I/O; wrong offset after an append by a different writer; reading a file still being downloaded/copied.
Understand the failure class
Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.
Related errors
- file ended before the requested read length
- file ended before the requested read length
- file ended before the requested read length
- file refused further writes
- bytes exceeds the byte whole-file limit; open the file and…
AI-assisted analysis of EpicGames/lore@074eb0b0d1 (2026-09-13).
Data as JSON: /api/errors/0fd13d9b651266ba.
Report an issue: GitHub.
Appendix: source
Thrown at lore-io/src/uring.rs:349
/// between passes and there is no thread to hold across them.
pub(crate) async fn read_exact_at(
&self,
file: Arc<File>,
len: usize,
offset: u64,
) -> std::io::Result<Bytes> {
// SAFETY: every byte up to `len` is filled before the buffer is frozen, and a short read
// returns an error rather than the buffer.
let mut buffer = unsafe { crate::buffer::uninit_buffer(len) };
let mut done = 0;
while done < len {
let entry = read_entry(&file, &mut buffer, done, len - done, offset + done as u64);
let (payload, result) = self.submit(entry, payload(buffer, &file))?.await;
buffer = payload.buffer;
match interpret(result)? {
Progress::Interrupted => {}
Progress::Bytes(0) => {
return Err(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
"file ended before the requested read length",
));
}
Progress::Bytes(read) => done += read,
}
}
Ok(buffer.freeze())
}
pub(crate) async fn write_at<B: StableBuf>(
&self,
file: Arc<File>,
buffer: B,
buffer_offset: usize,
len: usize,
offset: u64,
) -> std::io::Result<(B, usize)> {View on GitHub (pinned to 074eb0b0d1)