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

  1. Check file size >= offset + len before issuing the read.
  2. Reopen the file and retry after the writer/truncator settles.
  3. Fix offset/length calculations for the record layout.
  4. 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

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


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)