tokio-rs/tokio · error · io::Error

early eof

Error message

early eof

What it means

Runtime error from `read_exact_arc` (read_arc.rs:36). Like `AsyncReadExt::read_exact`, it must fill exactly `len` bytes; if the reader returns `0` (EOF) before that, it returns `io::ErrorKind::UnexpectedEof` and discards the partial buffer.

Source

Thrown at tokio-util/src/io/read_arc.rs:36

///
/// let arc = read_exact_arc(read, 4).await?;
///
/// assert_eq!(&arc[..], &[42; 4]);
/// # Ok(())
/// # }
/// ```
pub async fn read_exact_arc<R: AsyncRead>(read: R, len: usize) -> io::Result<Arc<[u8]>> {
    tokio::pin!(read);
    // TODO(MSRV 1.82): When bumping MSRV, switch to `Arc::new_uninit_slice(len)`. The following is
    // equivalent, and generates the same assembly, but works without requiring MSRV 1.82.
    let arc: Arc<[MaybeUninit<u8>]> = (0..len).map(|_| MaybeUninit::uninit()).collect();
    // TODO(MSRV future): Use `Arc::get_mut_unchecked` once it's stabilized.
    // SAFETY: We're the only owner of the `Arc`, and we keep the `Arc` valid throughout this loop
    // as we write through this reference.
    let mut buf = unsafe { &mut *(Arc::as_ptr(&arc) as *mut [MaybeUninit<u8>]) };
    while !buf.is_empty() {
        if read.read_buf(&mut buf).await? == 0 {
            return Err(io::Error::new(io::ErrorKind::UnexpectedEof, "early eof"));
        }
    }
    // TODO(MSRV 1.82): When bumping MSRV, switch to `arc.assume_init()`. The following is
    // equivalent, and generates the same assembly, but works without requiring MSRV 1.82.
    // SAFETY: This changes `[MaybeUninit<u8>]` to `[u8]`, and we've initialized all the bytes in
    // the loop above.
    Ok(unsafe { Arc::from_raw(Arc::into_raw(arc) as *const [u8]) })
}

View on GitHub (pinned to 625954f365)

Solutions

  1. Verify the requested `len` matches the actual available data / protocol-advertised length.
  2. Handle `UnexpectedEof` as a truncated-message protocol error (close/reconnect).
  3. Use `read_buf`/variable-length reading if the exact length is not guaranteed.
  4. Validate the length prefix against a sane maximum before issuing the exact read.

Example fix

// before: assuming the full payload arrives
let payload = read_exact_arc(stream, header.len as usize).await?;

// after: treat a short stream as a protocol error
let payload = read_exact_arc(stream, header.len as usize)
    .await
    .map_err(|e| match e.kind() {
        io::ErrorKind::UnexpectedEof => proto_err("truncated payload"),
        _ => e.into(),
    })?;
Defensive patterns

Strategy: try-catch

Validate before calling

// Validate the length prefix before the exact read
if header.len as usize > MAX_PAYLOAD { return Err(proto_err("length exceeds cap")); }
let n = header.len as usize;

Type guard

fn is_early_eof(e: &io::Error) -> bool { e.kind() == io::ErrorKind::UnexpectedEof }

Try / catch

let payload = read_exact_arc(stream, n).await.map_err(|e| {
    if is_early_eof(&e) { proto_err("truncated payload") } else { e.into() }
})?;

Prevention

When it happens

Trigger: Requesting `read_exact_arc(r, len)` when the source yields fewer than `len` bytes then EOF: a truncated length-prefixed payload, a short file, or an overestimated `len`.

Common situations: Peer sent fewer bytes than the length prefix implied; file shorter than expected; passing a too-large `len`; header advertised a body size that never fully arrived.

Related errors


AI-assisted analysis of tokio-rs/tokio@625954f365 (2026-08-11). Data as JSON: /api/errors/39bbb835ca3249e8. Report an issue: GitHub.