risingwavelabs/risingwave · error · anyhow::Error

No writer to close

Error message

No writer to close

What it means

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.

Solutions

  1. Confirm barrier(is_checkpoint=true) is only called once per epoch
  2. Check that write_batch (or partitioning logic) created the data-file writer before the checkpoint
  3. For intentionally empty commits, allow None data files instead of erroring, if that matches expected semantics
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure the epoch produced a writer before checkpointing
if inner.current_writer().is_none() { return Ok(None); }

Try / catch

match inner.close().await { Ok(Some(files)) => Some(inner.generate_commit_metadata(files)?), Ok(None) => None, Err(e) => return Err(e.into()) }

Prevention

When it happens

Trigger: 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.

Common situations: Checkpointing an empty epoch after a rescale or restart; double barrier handling; writer reuse bugs after a failed commit.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11). Data as JSON: /api/errors/b7d7eb4b37d9107d. Report an issue: GitHub.

Appendix: source

Thrown at src/connector/src/sink/iceberg/writer.rs:966

        inner.write_batch(chunk).await
    }

    /// Receive a barrier and mark the end of current epoch. When `is_checkpoint` is true, the sink
    /// writer should commit the current epoch.
    async fn barrier(&mut self, is_checkpoint: bool) -> Result<Option<SinkMetadata>> {
        let Self::Initialized(inner) = self else {
            unreachable!("IcebergSinkWriter should be initialized before barrier");
        };

        // Skip it if not checkpoint
        if !is_checkpoint {
            return Ok(None);
        }

        let data_files = inner
            .close()
            .await?
            .ok_or_else(|| anyhow!("No writer to close"))?;
        let res = inner.generate_commit_metadata(data_files)?;
        Ok(Some(res))
    }
}

/// Maximum size for column statistics (min/max values) in bytes.
/// Column statistics larger than this will be truncated to avoid metadata bloat.
/// This is especially important for large fields like JSONB, TEXT, BINARY, etc.
///
/// Fix for large column statistics in `DataFile` metadata that can cause OOM errors.
/// We truncate at the `DataFile` level (before serialization) by directly modifying
/// the public `lower_bounds` and `upper_bounds` fields.
///
/// This prevents metadata from ballooning to gigabytes when dealing with large
/// JSONB, TEXT, or BINARY fields, while still preserving statistics for small fields
/// that benefit from query optimization.
const MAX_COLUMN_STAT_SIZE: usize = 10240; // 10KB

View on GitHub (pinned to 6469eb736d)