tokio-rs/tokio · error · io::Error
failed to write the buffered data
Error message
failed to write the buffered data
What it means
Thrown by BufWriter's flush_buf when the wrapped writer's poll_write returns Ok(0) — i.e. it accepted zero bytes of buffered output. Tokio treats a successful-but-empty write as a hard failure because flushing cannot make progress, so the buffered data is effectively undeliverable. The error carries io::ErrorKind::WriteZero so callers can distinguish it from transient backpressure.
Source
Thrown at tokio/src/io/util/buf_writer.rs:66
/// Creates a new `BufWriter` with the specified buffer capacity.
pub fn with_capacity(cap: usize, inner: W) -> Self {
Self {
inner,
buf: Vec::with_capacity(cap),
written: 0,
seek_state: SeekState::Init,
}
}
fn flush_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
let mut me = self.project();
let len = me.buf.len();
let mut ret = Ok(());
while *me.written < len {
match ready!(me.inner.as_mut().poll_write(cx, &me.buf[*me.written..])) {
Ok(0) => {
ret = Err(io::Error::new(
io::ErrorKind::WriteZero,
"failed to write the buffered data",
));
break;
}
Ok(n) => *me.written += n,
Err(e) => {
ret = Err(e);
break;
}
}
}
if *me.written > 0 {
me.buf.drain(..*me.written);
}
*me.written = 0;
Poll::Ready(ret)
}View on GitHub (pinned to 625954f365)
Solutions
- Verify the underlying writer is still connected before flushing (e.g. check a connection flag or preceding read returning 0).
- Handle io::ErrorKind::WriteZero explicitly in the caller and treat it as a closed-sink condition rather than a generic error.
- If using a custom AsyncWrite, ensure poll_write never returns Ok(0) when the buffer is non-empty — return Poll::Pending instead and register the waker.
- Flush more frequently so the failure surfaces closer to its cause, or avoid wrapping an already-closed writer.
Example fix
// before
let mut w = BufWriter::new(stream);
w.write_all(data).await?;
w.flush().await?; // panics-on-err if peer closed
// after
match w.flush().await {
Ok(()) => {},
Err(e) if e.kind() == io::ErrorKind::WriteZero => {
// downstream closed; stop writing
break;
}
Err(e) => return Err(e),
} Defensive patterns
Strategy: try-catch
Validate before calling
// Before flushing, probe the writer with a 0-byte write or track a 'closed' flag
// set when a prior write returned Err(BrokenPipe) / read returned 0.
if writer.is_closed() { return Ok(()); }
writer.flush().await?; Type guard
fn is_write_zero(e: &io::Error) -> bool {
e.kind() == io::ErrorKind::WriteZero
} Try / catch
match writer.flush().await {
Ok(()) => {},
Err(e) if e.kind() == io::ErrorKind::WriteZero => {
// downstream sink closed; stop the write loop
break;
}
Err(e) => return Err(e.into()),
} Prevention
- Track the downstream connection state and stop flushing once a peer close is observed.
- When implementing a custom AsyncWrite, never return Ok(0) on a non-empty write — return Pending.
- Flush in smaller increments so closures surface near their cause.
- Don't wrap a writer you've already half-closed.
When it happens
Trigger: Calling .flush()/.shutdown() (or letting the internal buffer fill and auto-flush) on a tokio::io::BufWriter wrapping a sink that has been closed, half-closed, or that always returns 0 from poll_write. Directly observed when the downstream connection drops mid-write or a custom AsyncWrite returns Ok(0) erroneously.
Common situations: Writing to a TCP/Unix stream after the peer has closed its read side; a broken pipe whose SIGPIPE was suppressed; a custom AsyncWrite with a buggy poll_write; writing past EOF on a special file. Frequently surfaces in proxy/streaming code after the remote disconnects.
Related errors
- write zero byte into writer
- stream did not contain valid UTF-8
- early eof
- early eof
- utf-8 error (io::Error::new(io::ErrorKind::InvalidData, err)
AI-assisted analysis of tokio-rs/tokio@625954f365 (2026-08-11).
Data as JSON: /api/errors/5dcd77cbeb5f874a.
Report an issue: GitHub.