risingwavelabs/risingwave · error · StreamExecutorError

Channel closed: {0}

Error message

Channel closed: {0}

What it means

The ChannelClosed variant carries a human-readable message ('Channel closed: {0}') describing a closed internal channel inside the streaming executor. It indicates the executor tried to send or receive over a channel whose other end has been dropped, usually because a downstream/upstream actor has terminated.

Source

Thrown at src/stream/src/executor/error.rs:85

        BoxedError,
    ),

    #[error("Sink error: sink_id={1}, error: {0}")]
    SinkError(
        #[source]
        #[backtrace]
        SinkError,
        SinkId,
    ),

    #[error(transparent)]
    RpcError(
        #[from]
        #[backtrace]
        RpcError,
    ),

    #[error("Channel closed: {0}")]
    ChannelClosed(String),

    #[error(transparent)]
    ExchangeChannelClosed(
        #[from]
        #[backtrace]
        ExchangeChannelClosed,
    ),

    #[error("Failed to align barrier: expected `{0:?}` but got `{1:?}`")]
    AlignBarrier(Box<Barrier>, Box<Barrier>),

    #[error("Connector error: {0}")]
    ConnectorError(
        #[source]
        #[backtrace]
        BoxedError,
    ),

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Look at sibling actor logs to find why the peer actor terminated first (the root failure is usually upstream).
  2. Check for panics or OOM kills in the compute node logs.
  3. Rely on actor restart/failover to rebuild channels; investigate repeated closures for deterministic failures.
  4. Ensure barrier alignment and termination logic closes channels gracefully rather than dropping them.
Defensive patterns

Strategy: try-catch

Validate before calling

// before sending, check channel liveness
if sender.is_closed() {
    tracing::warn!("downstream channel already closed; skipping send");
    return Ok(());
}

Type guard

fn is_channel_closed(e: &StreamExecutorError) -> bool {
    e.variant_name() == "ChannelClosed"
}

Try / catch

if let Err(e) = res {
    if e.variant_name() == "ChannelClosed" {
        // peer actor gone; let failover rebuild the graph
        return Ok(());
    }
    return Err(e.into());
}

Prevention

When it happens

Trigger: Created via ErrorKind::ChannelClosed(format!(...)) at points where the executor finds its exchange channel handle closed; e.g. sending barriers or chunks after the remote actor exited.

Common situations: A downstream actor panicked or failed and its channel receiver was dropped; actor rescale/kill during runtime; shutdown races where data is still in flight.

Related errors


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