quickwit-oss/quickwit · error · io::Error (UnexpectedEof)
body stream ended after skipping {copied} bytes; expected to
Error message
body stream ended after skipping {copied} bytes; expected to skip {n} more What it means
`skip_forward` discards `n` bytes of the parquet stream by copying them into a sink. If the body yields fewer than `n` bytes before EOF, the reader cannot land on the next page boundary and returns UnexpectedEof from `next_page`.
Source
Thrown at quickwit/quickwit-parquet-engine/src/storage/streaming_reader.rs:604
Ok(())
}
/// Discard `n` bytes by reading and dropping them. Bytes already in
/// `pending` are drained first; remaining bytes are read from `body`.
async fn skip_forward(state: &mut ReadingState, mut n: usize) -> io::Result<()> {
if n == 0 {
return Ok(());
}
let from_pending = n.min(state.pending.len());
state.pending.drain(..from_pending);
n -= from_pending;
if n == 0 {
return Ok(());
}
let mut sink = tokio::io::sink();
let copied = tokio::io::copy(&mut (&mut state.body).take(n as u64), &mut sink).await?;
if copied < n as u64 {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
format!("body stream ended after skipping {copied} bytes; expected to skip {n} more",),
));
}
Ok(())
}
/// Compute the (start, end) offsets of the body byte range — first
/// column chunk's start to last column chunk's end. Returns
/// `(0, 0)` for files with zero row groups.
fn compute_body_range(metadata: &ParquetMetaData) -> (u64, u64) {
let num_rgs = metadata.num_row_groups();
if num_rgs == 0 {
return (0, 0);
}
let first_rg = metadata.row_group(0);
if first_rg.num_columns() == 0 {
return (0, 0);View on GitHub (pinned to a39730c5cd)
Solutions
- Retry the operation with a fresh connection/range request.
- Validate the split's stored size and checksum; re-upload if the object is truncated.
- Inspect intermediate proxies/timeouts if the failure is reproducible at a fixed byte offset.
- If persistent, re-index or restore the split from a known-good copy.
Defensive patterns
Strategy: retry
Type guard
fn is_skip_eof(err: &std::io::Error) -> bool { err.kind() == std::io::ErrorKind::UnexpectedEof } Try / catch
if let Err(e) = next_page().await {
if e.kind() == std::io::ErrorKind::UnexpectedEof {
// reopen a fresh connection/range request and retry
retry_with_backoff(|| reopen_and_next_page()).await?;
} else { return Err(e.into()); }
} Prevention
- Validate split file size/checksum before serving queries from it.
- Use bounded retries with fresh connections for long-lived body streams.
- Watch for proxies terminating mid-transfer; configure keepalives.
When it happens
Trigger: Calling next_page when the remote body ends early during a skip: truncated split file, connection dropped mid-skip, or a range request returning fewer bytes than requested.
Common situations: Same network reliability issues as read failures: LB idle timeouts, reset connections, object storage returning short reads for corrupted or concurrently-deleted objects.
Understand the failure class
Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.
Related errors
- body stream ended with {} bytes pending; expected {target}
- failed to get message from consumer: {:?}
- no server currently available
- failed to parse host: `{host}`
- failed to parse address `{}`: hostname is invalid
AI-assisted analysis of quickwit-oss/quickwit@a39730c5cd (2026-09-08).
Data as JSON: /api/errors/828eabf9f9a19aa4.
Report an issue: GitHub.