risingwavelabs/risingwave · error

Should not get split info from incomplete source scan info

Error message

Should not get split info from incomplete source scan info

What it means

`SourceScanInfo::split_info` (src/frontend/src/scheduler/plan_fragmenter.rs:373) returns the splits only when the scan info is in the `Complete` state. Calling it on an `Incomplete` variant returns an `SchedulerError::Internal` because splits have not yet been enumerated — the caller violated the expected state transition (complete the source scan first).

Source

Thrown at src/frontend/src/scheduler/plan_fragmenter.rs:373

impl SourceScanInfo {
    pub fn new(fetch_info: SourceFetchInfo) -> Self {
        Self::Incomplete(fetch_info)
    }

    pub async fn complete(self, batch_parallelism: usize) -> SchedulerResult<Self> {
        match self {
            SourceScanInfo::Incomplete(fetch_info) => fetch_info.complete(batch_parallelism).await,
            SourceScanInfo::Unpartitioned(data) => data.complete(batch_parallelism),
            SourceScanInfo::Complete(_) => {
                unreachable!("Never call complete when SourceScanInfo is already complete")
            }
        }
    }

    pub fn split_info(&self) -> SchedulerResult<&Vec<SplitImpl>> {
        match self {
            Self::Incomplete(_) => Err(SchedulerError::Internal(anyhow!(
                "Should not get split info from incomplete source scan info"
            ))),
            Self::Unpartitioned(_) => Err(SchedulerError::Internal(anyhow!(
                "Should not get split info from unpartitioned source scan info"
            ))),
            Self::Complete(split_info) => Ok(split_info),
        }
    }
}

impl UnpartitionedData {
    fn complete(self, batch_parallelism: usize) -> SchedulerResult<SourceScanInfo> {
        let splits = match self {
            UnpartitionedData::Iceberg { task, limit } => {
                IcebergScanTaskPlanner::plan_splits(task, batch_parallelism, limit)?
                    .into_iter()
                    .map(SplitImpl::Iceberg)
                    .collect()

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Ensure the source scan info is driven through `complete()` before reading `split_info()`.
  2. Check why split enumeration failed earlier — fix the underlying connector/split-listing error.
  3. Match on the enum (`Complete`) in caller code instead of assuming completeness.
  4. Report if triggered by a stock query — it is a state-machine violation in the scheduler.

Example fix

// before
let splits = source_scan_info.split_info()?;
// after
let splits = match source_scan_info {
    SourceScanInfo::Complete(_) => source_scan_info.split_info()?,
    _ => return Err(anyhow!("source scan not completed before scheduling")),
};
Defensive patterns

Strategy: type-guard

Type guard

fn is_complete(info: &SourceScanInfo) -> bool {
    matches!(info, SourceScanInfo::Complete(_))
}

Try / catch

match source_scan_info {
    SourceScanInfo::Complete(_) => source_scan_info.split_info()?,
    SourceScanInfo::Incomplete(_) => return Err(anyhow!("source scan incomplete; complete() first")),
    _ => Default::default(),
}

Prevention

When it happens

Trigger: Calling `source_scan_info.split_info()` before the fragmenter finished split enumeration for a partitionable source (e.g. during scheduling of a source scan whose `complete()` step was skipped or failed).

Common situations: Internal scheduling bugs where `IncompleteSourceScanInfo` is carried into split-consuming code paths, e.g. after a connector split-listing failure left the scan info incomplete.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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