{"record":{"id":"2d163807f9091c1f","repo":"tokio-rs/tokio","slug":"write-zero-byte-into-writer","errorCode":null,"errorMessage":"write zero byte into writer","messagePattern":"write zero byte into writer","errorType":"exception","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"tokio/src/io/util/copy.rs","lineNumber":173,"sourceCode":"                }\n            }\n\n            // If our buffer has some data, let's write it out!\n            while self.pos < self.cap {\n                let i = ready!(self.poll_write_buf(cx, reader.as_mut(), writer.as_mut()))?;\n                #[cfg(any(\n                    feature = \"fs\",\n                    feature = \"io-std\",\n                    feature = \"net\",\n                    feature = \"process\",\n                    feature = \"rt\",\n                    feature = \"signal\",\n                    feature = \"sync\",\n                    feature = \"time\",\n                ))]\n                coop.made_progress();\n                if i == 0 {\n                    return Poll::Ready(Err(io::Error::new(\n                        io::ErrorKind::WriteZero,\n                        \"write zero byte into writer\",\n                    )));\n                } else {\n                    self.pos += i;\n                    self.amt += i as u64;\n                    self.need_flush = true;\n                }\n            }\n\n            // If pos larger than cap, this loop will never stop.\n            // In particular, user's wrong poll_write implementation returning\n            // incorrect written length may lead to thread blocking.\n            debug_assert!(\n                self.pos <= self.cap,\n                \"writer returned length larger than input slice\"\n            );\n","sourceCodeStart":155,"sourceCodeEnd":191,"githubUrl":"https://github.com/tokio-rs/tokio/blob/625954f365727668cb02d04172b34f1149637728/tokio/src/io/util/copy.rs#L155-L191","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Match on io::ErrorKind::WriteZero at the copy call site and treat it as 'destination closed' — typically tear down the bridging task.","Ensure the destination is alive for the whole copy (don't drop/close the write half prematurely).","If the writer is custom, return Poll::Pending (registering cx.waker()) rather than Ok(0) when not ready.","For copy_bidirectional, half-close gracefully: on WriteZero from one direction, finish draining the other direction before exiting."],"exampleFix":"// before\nlet n = tokio::io::copy(&mut reader, &mut writer).await?;\n\n// after\nmatch tokio::io::copy(&mut reader, &mut writer).await {\n    Ok(n) => Ok(n),\n    Err(e) if e.kind() == io::ErrorKind::WriteZero => {\n        // writer closed mid-copy; reader may still have data\n        Ok(0)\n    }\n    Err(e) => Err(e),\n}","handlingStrategy":"try-catch","validationCode":"// Ensure the destination is writable before copying by tracking its liveness.\n// There is no portable pre-check; the recommended guard is at the call site.","typeGuard":"fn is_copy_write_zero(e: &io::Error) -> bool {\n    e.kind() == io::ErrorKind::WriteZero\n}","tryCatchPattern":"match tokio::io::copy(&mut r, &mut w).await {\n    Ok(n) => Ok(n),\n    Err(e) if e.kind() == io::ErrorKind::WriteZero => {\n        // destination closed mid-copy; tear down the bridge\n        Ok(0)\n    }\n    Err(e) => Err(e.into()),\n}","preventionTips":["Keep the destination alive for the entire copy; don't drop the write half early.","For custom AsyncWrite on the destination, return Pending instead of Ok(0).","In copy_bidirectional, finish draining the surviving direction on WriteZero.","Log the bytes copied before the failure for diagnostics."],"tags":["io","copy","write-zero","tokio"],"backgroundTag":null,"analyzedSha":"625954f365727668cb02d04172b34f1149637728","analyzedAt":"2026-08-11T17:46:45.378Z","contentChangedAt":"2026-08-11T17:46:45.378Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}