{"record":{"id":"fa44d70971f8044a","repo":"rustfs/rustfs","slug":"erasure-reader-workspace-missing","errorCode":null,"errorMessage":"erasure reader workspace missing","messagePattern":"erasure reader workspace missing","errorType":"exception","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"crates/ecstore/src/erasure/coding/decode_reader.rs","lineNumber":235,"sourceCode":"    fn extend_reusable_output_bufs(&mut self, bufs: Vec<Vec<u8>>) {\n        for buf in bufs {\n            self.push_reusable_output_buf(buf);\n        }\n    }\n\n    fn fill_worker_tx(&mut self) -> io::Result<mpsc::Sender<FillRequest>> {\n        if self.worker.is_none() {\n            let Some(source) = self.source.take() else {\n                return Err(io::Error::new(ErrorKind::BrokenPipe, \"erasure reader source missing\"));\n            };\n            let Some(engine) = self.engine.take() else {\n                self.source = Some(source);\n                return Err(io::Error::new(ErrorKind::BrokenPipe, \"erasure reader engine missing\"));\n            };\n            let Some(workspace) = self.workspace.take() else {\n                self.source = Some(source);\n                self.engine = Some(engine);\n                return Err(io::Error::new(ErrorKind::BrokenPipe, \"erasure reader workspace missing\"));\n            };\n\n            let (tx, rx) = mpsc::channel(1);\n            rustfs_io_metrics::record_get_object_fill_worker_started(self.metrics_path, self.fill_policy.as_str());\n            let task = tokio::spawn(run_fill_worker(\n                source,\n                engine,\n                workspace,\n                self.fill_policy,\n                self.metrics_path,\n                self.stage_metrics_enabled,\n                rx,\n            ));\n            self.worker = Some(FillWorker { tx, task });\n        }\n\n        self.worker\n            .as_ref()","sourceCodeStart":217,"sourceCodeEnd":253,"githubUrl":"https://github.com/rustfs/rustfs/blob/9e6e02ea09c86bedf44c7bd64a74ea02a0cff1de/crates/ecstore/src/erasure/coding/decode_reader.rs#L217-L253","documentation":"Internal invariant of ErasureReader::fill_worker_tx: the decode workspace is taken from an Option when the fill worker spawns; BrokenPipe is reported if a spawn is needed but the workspace is gone (source and engine are restored first). Reaching it means the reader was already drained of its components by a previous worker — reuse past the reader's lifetime.","triggerScenarios":"A fill poll on a reader whose workspace was moved into an earlier, now-terminated worker — reading after a terminal error or after the stream finished.","commonSituations":"Retry loops reusing the reader; keeping readers in pools/registers beyond one object stream.","solutions":["Scope each ErasureReader to a single GET stream; recreate on retry","Propagate terminal errors and drop the reader immediately","Report as a bug if it occurs without reader reuse"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":"fn is_reader_workspace_missing(err: &std::io::Error) -> bool {\n    err.kind() == std::io::ErrorKind::BrokenPipe\n        && err.to_string().contains(\"workspace missing\")\n}","tryCatchPattern":"match reader.read(&mut buf).await {\n    Err(e) if is_reader_workspace_missing(&e) => {\n        // reader state consumed: create a new ErasureReader for the next attempt\n    }\n    r => r,\n}","preventionTips":["Scope readers to one stream lifecycle","Never read after a terminal fill error","Report occurrences as internal-state bugs"],"tags":["erasure-coding","streaming-read","broken-pipe","reader-lifetime","internal-invariant"],"backgroundTag":"reader-reuse-after-close","analyzedSha":"9e6e02ea09c86bedf44c7bd64a74ea02a0cff1de","analyzedAt":"2026-08-16T20:34:17.560Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}