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

write zero byte into writer

Error message

write zero byte into writer

What it means

Thrown by the copy/copy_bidirectional pump loop when poll_write_buf returns 0 bytes written while there is still buffered input to flush. Tokio signals io::ErrorKind::WriteZero because the destination cannot accept any data, so continuing would loop forever — the adjacent debug_assert even warns that a buggy poll_write returning 0 will block. It is a terminal condition for the copy future.

Source

Thrown at tokio/src/io/util/copy.rs:173

                }
            }

            // If our buffer has some data, let's write it out!
            while self.pos < self.cap {
                let i = ready!(self.poll_write_buf(cx, reader.as_mut(), writer.as_mut()))?;
                #[cfg(any(
                    feature = "fs",
                    feature = "io-std",
                    feature = "net",
                    feature = "process",
                    feature = "rt",
                    feature = "signal",
                    feature = "sync",
                    feature = "time",
                ))]
                coop.made_progress();
                if i == 0 {
                    return Poll::Ready(Err(io::Error::new(
                        io::ErrorKind::WriteZero,
                        "write zero byte into writer",
                    )));
                } else {
                    self.pos += i;
                    self.amt += i as u64;
                    self.need_flush = true;
                }
            }

            // If pos larger than cap, this loop will never stop.
            // In particular, user's wrong poll_write implementation returning
            // incorrect written length may lead to thread blocking.
            debug_assert!(
                self.pos <= self.cap,
                "writer returned length larger than input slice"
            );

View on GitHub (pinned to 625954f365)

Solutions

  1. Match on io::ErrorKind::WriteZero at the copy call site and treat it as 'destination closed' — typically tear down the bridging task.
  2. Ensure the destination is alive for the whole copy (don't drop/close the write half prematurely).
  3. If the writer is custom, return Poll::Pending (registering cx.waker()) rather than Ok(0) when not ready.
  4. For copy_bidirectional, half-close gracefully: on WriteZero from one direction, finish draining the other direction before exiting.

Example fix

// before
let n = tokio::io::copy(&mut reader, &mut writer).await?;

// after
match tokio::io::copy(&mut reader, &mut writer).await {
    Ok(n) => Ok(n),
    Err(e) if e.kind() == io::ErrorKind::WriteZero => {
        // writer closed mid-copy; reader may still have data
        Ok(0)
    }
    Err(e) => Err(e),
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Ensure the destination is writable before copying by tracking its liveness.
// There is no portable pre-check; the recommended guard is at the call site.

Type guard

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

Try / catch

match tokio::io::copy(&mut r, &mut w).await {
    Ok(n) => Ok(n),
    Err(e) if e.kind() == io::ErrorKind::WriteZero => {
        // destination closed mid-copy; tear down the bridge
        Ok(0)
    }
    Err(e) => Err(e.into()),
}

Prevention

When it happens

Trigger: tokio::io::copy, copy_bidirectional, or the internal CopyBuffer driving a reader→writer transfer where the writer returns Ok(0) on a non-empty write request. Happens when the destination socket/pipe/file is closed or full-and-closed before the source is exhausted.

Common situations: Proxying between two TCP connections where the client/server closes early; piping into a process that exited (broken pipe); a writer fd that hit EOF; a custom AsyncWrite returning Ok(0) instead of Pending. The reader side still has data, but the sink is gone.

Related errors


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