EpicGames/lore · error · io::Error

file ended before the requested read length

Error message

file ended before the requested read length

What it means

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.

Solutions

  1. Verify offset + len <= file metadata len() before calling read_exact_at.
  2. Re-check the file size and re-read after the writer finishes (use file locking or wait for a completion marker).
  3. Fix offset arithmetic; ensure offsets are in bytes and point at data actually written.
  4. Fall back to reading whatever is available with a plain read_at if a short read is acceptable.

Example fix

// before
file.read_exact_at(&mut header, 0).await?; // UnexpectedEof if file < header.len()
// after
let len = file.metadata().await?.len();
if (header.len() as u64) > len {
    return Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "file too short"));
}
file.read_exact_at(&mut header, 0).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, or treat as truncated file */ }
    Err(e) => return Err(e.into()),
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

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/d6da404318e16238. Report an issue: GitHub.

Appendix: source

Thrown at lore-io/src/psync.rs:83

    /// job already owns the buffer and the thread, so returning to the caller between syscalls
    /// would buy nothing and pay two handoffs. This is the shape the whole-file read below and
    /// the file scan this backend replaces both use.
    pub(crate) async fn read_exact_at(
        &self,
        file: Arc<File>,
        len: usize,
        offset: u64,
    ) -> std::io::Result<Bytes> {
        SyscallPool::global()
            .submit(move || {
                // SAFETY: every byte up to `len` is filled before returning, 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 read = read_at_impl(&file, &mut buffer[done..len], offset + done as u64)?;
                    if read == 0 {
                        return Err(std::io::Error::new(
                            std::io::ErrorKind::UnexpectedEof,
                            "file ended before the requested read length",
                        ));
                    }
                    done += read;
                }
                Ok(buffer.freeze())
            })
            .await
    }

    /// Writes all `len` bytes, looping inside one dispatch. See [`Self::read_exact_at`].
    pub(crate) async fn write_all_at<B: StableBuf>(
        &self,
        file: Arc<File>,
        buffer: B,
        len: usize,
        offset: u64,

View on GitHub (pinned to 074eb0b0d1)