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

  1. Verify the underlying writer is still connected before flushing (e.g. check a connection flag or preceding read returning 0).
  2. Handle io::ErrorKind::WriteZero explicitly in the caller and treat it as a closed-sink condition rather than a generic error.
  3. 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.
  4. 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

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


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