{"record":{"id":"1cae18d922ef6cb1","repo":"pola-rs/polars","slug":"unexpectedeof","errorCode":"UnexpectedEof","errorMessage":"Cannot write to a finished stream","messagePattern":"Cannot write to a finished stream","errorType":"exception","errorClass":"PolarsError","httpStatus":null,"severity":"error","filePath":"crates/polars-arrow/src/io/ipc/write/stream.rs","lineNumber":93,"sourceCode":"            ipc_message: schema_to_bytes(\n                schema,\n                self.ipc_fields.as_ref().unwrap(),\n                self.custom_schema_metadata.as_deref(),\n            ),\n            arrow_data: vec![],\n        };\n        write_message(&mut self.writer, &encoded_message)?;\n        Ok(())\n    }\n\n    /// Writes [`RecordBatchT`] to the stream\n    pub fn write(\n        &mut self,\n        columns: &RecordBatchT<Box<dyn Array>>,\n        ipc_fields: Option<&[IpcField]>,\n    ) -> PolarsResult<()> {\n        if self.finished {\n            let io_err = std::io::Error::new(\n                std::io::ErrorKind::UnexpectedEof,\n                \"Cannot write to a finished stream\".to_string(),\n            );\n            return Err(PolarsError::from(io_err));\n        }\n\n        // we can't make it a closure because it borrows (and it can't borrow mut and non-mut below)\n        #[allow(clippy::or_fun_call)]\n        let fields = ipc_fields.unwrap_or(self.ipc_fields.as_ref().unwrap());\n\n        let (encoded_dictionaries, encoded_message) = encode_chunk(\n            columns,\n            fields,\n            &mut self.dictionary_tracker,\n            &self.write_options,\n        )?;\n\n        for encoded_dictionary in encoded_dictionaries {","sourceCodeStart":75,"sourceCodeEnd":111,"githubUrl":"https://github.com/pola-rs/polars/blob/df599052daf96e7a9cc30a3b0c6bd25d6947e3c0/crates/polars-arrow/src/io/ipc/write/stream.rs#L75-L111","documentation":"Returned by StreamWriter::write in polars-arrow's IPC stream writer when a record batch is written after StreamWriter::finish has already been called. finish() writes the zero-length continuation that terminates an Arrow IPC stream and sets finished = true; every later write is rejected with this error. Note the io::ErrorKind is UnexpectedEof, which is misleading - nothing ran out of input, the writer is simply closed for further writes.","triggerScenarios":"Calling StreamWriter::write (or any higher-level batch-writing loop) after finish() was already invoked - e.g. an error path that finishes the stream and then falls through to write remaining batches, or a background producer still writing while the main thread closed the stream.","commonSituations":"Retry loops that reuse one writer across attempts; early-exit code that calls finish() inside the loop body instead of after it; concurrent producer/consumer designs where one side closes the stream; refactors that moved finish() into a helper invoked per-chunk.","solutions":["Restructure so finish() runs exactly once after the last write, and return early from error paths once finished","If more batches can still arrive, buffer them and write them to a fresh StreamWriter on a new stream/file - an IPC stream cannot be reopened","Track a finished flag next to the writer and skip or queue writes once it is set","If you wrap StreamWriter, expose an is_finished() accessor so callers can check before writing"],"exampleFix":"// before\nfor batch in batches {\n    if let Err(e) = writer.write(&batch, None) {\n        writer.finish()?;\n    }\n    // still writing after finish -> 'Cannot write to a finished stream'\n}\n\n// after\nlet mut finished = false;\nfor batch in batches {\n    if !finished {\n        writer.write(&batch, None)?;\n    }\n}\nwriter.finish()?;","handlingStrategy":"validation","validationCode":"// Keep finish-state next to the writer and check before every write\nlet mut finished = false;\nfor batch in batches {\n    if finished {\n        continue; // or buffer / error explicitly\n    }\n    writer.write(&batch, None)?;\n}\nwriter.finish()?;\nfinished = true;","typeGuard":null,"tryCatchPattern":"match result {\n    Err(PolarsError::IO(e)) if e.kind() == std::io::ErrorKind::UnexpectedEof\n        && e.to_string().contains(\"finished stream\") => break, // stop writing loop\n    other => other?,\n}","preventionTips":["Call finish() exactly once, at the outermost scope that owns the writer","Return early from error paths once the stream is finished","Never share one StreamWriter across retry attempts - create a new one per stream","Wrap StreamWriter in your own type that tracks and enforces finished state"],"tags":["rust","polars","arrow","ipc","stream","state-machine"],"backgroundTag":null,"analyzedSha":"df599052daf96e7a9cc30a3b0c6bd25d6947e3c0","analyzedAt":"2026-08-16T12:10:03.978Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}