{"record":{"id":"b7d7eb4b37d9107d","repo":"risingwavelabs/risingwave","slug":"no-writer-to-close","errorCode":null,"errorMessage":"No writer to close","messagePattern":"No writer to close","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src/connector/src/sink/iceberg/writer.rs","lineNumber":966,"sourceCode":"        inner.write_batch(chunk).await\n    }\n\n    /// Receive a barrier and mark the end of current epoch. When `is_checkpoint` is true, the sink\n    /// writer should commit the current epoch.\n    async fn barrier(&mut self, is_checkpoint: bool) -> Result<Option<SinkMetadata>> {\n        let Self::Initialized(inner) = self else {\n            unreachable!(\"IcebergSinkWriter should be initialized before barrier\");\n        };\n\n        // Skip it if not checkpoint\n        if !is_checkpoint {\n            return Ok(None);\n        }\n\n        let data_files = inner\n            .close()\n            .await?\n            .ok_or_else(|| anyhow!(\"No writer to close\"))?;\n        let res = inner.generate_commit_metadata(data_files)?;\n        Ok(Some(res))\n    }\n}\n\n/// Maximum size for column statistics (min/max values) in bytes.\n/// Column statistics larger than this will be truncated to avoid metadata bloat.\n/// This is especially important for large fields like JSONB, TEXT, BINARY, etc.\n///\n/// Fix for large column statistics in `DataFile` metadata that can cause OOM errors.\n/// We truncate at the `DataFile` level (before serialization) by directly modifying\n/// the public `lower_bounds` and `upper_bounds` fields.\n///\n/// This prevents metadata from ballooning to gigabytes when dealing with large\n/// JSONB, TEXT, or BINARY fields, while still preserving statistics for small fields\n/// that benefit from query optimization.\nconst MAX_COLUMN_STAT_SIZE: usize = 10240; // 10KB\n","sourceCodeStart":948,"sourceCodeEnd":984,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/connector/src/sink/iceberg/writer.rs#L948-L984","documentation":"During a checkpoint barrier, inner.close() returns the writer's rolling data-file writer. None means the inner writer had no active writer to flush/close, so RisingWave cannot produce commit metadata for this epoch and fails with this anyhow error. It indicates the writer produced no data-file writer for a checkpointed epoch.","triggerScenarios":"barrier(is_checkpoint=true) invoked when the IcebergSinkWriterInner has no current data-file writer — e.g. an epoch that received no rows was still expected to commit, or the inner writer was already closed/consumed by a previous checkpoint.","commonSituations":"Checkpointing an empty epoch after a rescale or restart; double barrier handling; writer reuse bugs after a failed commit.","solutions":["Confirm barrier(is_checkpoint=true) is only called once per epoch","Check that write_batch (or partitioning logic) created the data-file writer before the checkpoint","For intentionally empty commits, allow None data files instead of erroring, if that matches expected semantics"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// ensure the epoch produced a writer before checkpointing\nif inner.current_writer().is_none() { return Ok(None); }","typeGuard":null,"tryCatchPattern":"match inner.close().await { Ok(Some(files)) => Some(inner.generate_commit_metadata(files)?), Ok(None) => None, Err(e) => return Err(e.into()) }","preventionTips":["Call barrier(is_checkpoint=true) exactly once per epoch","Write data (or explicitly handle empty epochs) before checkpointing","Log epoch state at checkpoint time to catch double-close bugs"],"tags":["rust","iceberg","sink","checkpoint"],"backgroundTag":"internal-invariant-violation","analyzedSha":"6469eb736d691e8e9b8a419a57edd6429ca77417","analyzedAt":"2026-09-11T21:06:21.487Z","contentChangedAt":"2026-09-11T21:06:21.487Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}