{"record":{"id":"b8f602f9d771d0cf","repo":"rustfs/rustfs","slug":"cannot-write-to-finalized-writer","errorCode":null,"errorMessage":"Cannot write to finalized writer","messagePattern":"Cannot write to finalized writer","errorType":"exception","errorClass":"std::io::Error","httpStatus":null,"severity":"error","filePath":"crates/io-core/src/writer.rs","lineNumber":265,"sourceCode":"\nimpl std::fmt::Debug for BytesMutWriter {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"BytesMutWriter\")\n            .field(\"buffer_len\", &self.buffer.len())\n            .field(\"buffer_capacity\", &self.buffer.capacity())\n            .field(\"bytes_written\", &self.bytes_written)\n            .field(\"finalized\", &self.finalized)\n            .finish()\n    }\n}\n\n/// AsyncWrite implementation for BytesMutWriter.\n///\n/// This allows the writer to be used with tokio's async I/O utilities.\nimpl AsyncWrite for BytesMutWriter {\n    fn poll_write(mut self: Pin<&mut Self>, _cx: &mut Context<'_>, buf: &[u8]) -> Poll<Result<usize, tokio::io::Error>> {\n        if self.finalized {\n            return Poll::Ready(Err(tokio::io::Error::new(\n                tokio::io::ErrorKind::WriteZero,\n                \"Cannot write to finalized writer\",\n            )));\n        }\n\n        let len = buf.len();\n        self.buffer.put_slice(buf);\n        self.bytes_written += len;\n        Poll::Ready(Ok(len))\n    }\n\n    fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), tokio::io::Error>> {\n        // Nothing to flush for in-memory buffer\n        Poll::Ready(Ok(()))\n    }\n\n    fn poll_shutdown(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), tokio::io::Error>> {\n        self.finalized = true;","sourceCodeStart":247,"sourceCodeEnd":283,"githubUrl":"https://github.com/rustfs/rustfs/blob/9e6e02ea09c86bedf44c7bd64a74ea02a0cff1de/crates/io-core/src/writer.rs#L247-L283","documentation":"`BytesMutWriter` is a one-shot async writer: once `finalize()` flips the internal `finalized` flag, any further `poll_write` returns `WriteZero` with 'Cannot write to finalized writer'. The type enforces a single write session — data written after finalization would silently miss whatever the finalize step committed (checksums, trailers, fsync), so it refuses instead.","triggerScenarios":"Calling `write_all`/`poll_write` on a `BytesMutWriter` after `finalize()` succeeded: retry loops that resume writing after a completed attempt, a shared writer handed to a second producer after commit, or a duplex flow that keeps writing after shutdown/finalize was triggered.","commonSituations":"Request handlers pooling/reusing writers to avoid allocation; wrappers (e.g. compression or tar streams) that flush trailing bytes after the underlying writer was finalized; error paths that finalize early then bubble back into the write loop.","solutions":["Create a fresh `BytesMutWriter` for every payload; treat finalize as consuming the writer.","Audit retry/resume logic: on retry after a finalized attempt, start a new writer rather than continuing.","If the type exposes the flag, assert `!finalized` before writing as a debug guard.","Restructure wrapper streams so nothing writes after they call finalize on the inner writer."],"exampleFix":"// before: retry loop reuses a finalized writer\nif let Err(e) = write_all(&mut writer, &data).await {\n    writer.finalize()?; // committed\n    writer.write_all(b\"trailer\").await?; // Err: Cannot write to finalized writer\n}\n\n// after: one writer per session\nlet mut writer = BytesMutWriter::new();\nwriter.write_all(&data).await?;\nwriter.write_all(b\"trailer\").await?;\nwriter.finalize()?; // finalize last, then drop","handlingStrategy":"validation","validationCode":"// One-shot discipline: finalize consumes the writer\nasync fn commit(w: &mut BytesMutWriter, data: &[u8]) -> io::Result<()> {\n    if w.is_finalized() { return Err(already_finalized()); } // if exposed\n    w.write_all(data).await?;\n    w.finalize()\n}","typeGuard":"// Model the session in types so writes-after-finalize cannot compile\nenum Open; enum Finalized;\nstruct Writer<S> { _s: S }\n// Writer<Open> has write()+finalize() -> Writer<Finalized>; Writer<Finalized> has neither","tryCatchPattern":"// On WriteZero 'Cannot write to finalized writer': abort this attempt and\n// restart with a fresh writer; never try to un-finalize.\nif e.kind() == std::io::ErrorKind::WriteZero { writer = BytesMutWriter::new(); retry(); }","preventionTips":["Create a new writer per payload/retry attempt","Treat finalize() as the last operation; drop the writer afterwards","Audit wrapper streams for writes after they finalize the inner writer"],"tags":["rust","writer","state-machine","async","misuse"],"backgroundTag":"write-after-close","analyzedSha":"9e6e02ea09c86bedf44c7bd64a74ea02a0cff1de","analyzedAt":"2026-08-16T20:34:17.560Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}