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
- Verify the requested `len` matches the actual available data / protocol-advertised length.
- Handle `UnexpectedEof` as a truncated-message protocol error (close/reconnect).
- Use `read_buf`/variable-length reading if the exact length is not guaranteed.
- 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
- Validate the length prefix against a sane maximum before requesting an exact read.
- Treat `UnexpectedEof` as a protocol/truncation error, not a transient retry.
- Use variable-length reads when the size is not guaranteed.
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
- bytes remaining on stream
- failed to write frame to transport
- frame size too big
- provided length would overflow after adjustment
- Unable to decode input as UTF8
AI-assisted analysis of tokio-rs/tokio@625954f365 (2026-08-11).
Data as JSON: /api/errors/39bbb835ca3249e8.
Report an issue: GitHub.