FuelLabs/fuel-core · warning
The block height subscription channel was closed: {:?}
Error message
The block height subscription channel was closed: {:?} What it means
Subscriber::wait_for_block_height registers a oneshot channel in a BTreeMap keyed by Reverse(block_height) and awaits it. The future resolves with Err (channel closed) when every stored Sender is dropped without sending — the sender lives in the subscription service's handler map, so closure means the service dropped the handlers (e.g., shutdown, cleanup, or map replacement) before the height was reached.
Source
Thrown at crates/fuel-core/src/graphql_api/block_height_subscription.rs:76
) -> anyhow::Result<()> {
let future = {
let mut inner_map = self.inner.write();
if inner_map.current_block_height >= block_height {
return Ok(());
}
let (tx, rx) = oneshot::channel();
let handlers = inner_map
.tx_handles
.entry(Reverse(block_height))
.or_default();
handlers.push(tx);
rx
};
future.await.map_err(|e| {
anyhow::anyhow!("The block height subscription channel was closed: {:?}", e)
})
}
pub fn current_block_height(&self) -> BlockHeight {
self.inner.read().current_block_height
}
}
#[derive(Debug, Default)]
struct HandlersMapInner {
tx_handles: BTreeMap<Reverse<BlockHeight>, Vec<oneshot::Sender<()>>>,
current_block_height: BlockHeight,
}
impl HandlersMapInner {
fn new(current_block_height: BlockHeight) -> Self {
Self {
tx_handles: BTreeMap::new(),View on GitHub (pinned to b9d4d170da)
Solutions
- Retry the wait after confirming the node is still running and producing/importing blocks.
- Before waiting, short-circuit if current_block_height() already satisfies the target.
- Treat the error as transient and re-subscribe rather than as data corruption.
Example fix
// before
subscriber.wait_for_block_height(h).await?;
// after
if subscriber.current_block_height() >= h {
return Ok(());
}
subscriber.wait_for_block_height(h).await?; Defensive patterns
Strategy: retry
Validate before calling
// Fast-path when the height is already reached; the channel only closes on service teardown.
if subscriber.current_block_height() >= wanted_height {
return Ok(());
}
subscriber.wait_for_block_height(wanted_height).await?; Try / catch
loop {
if subscriber.current_block_height() >= wanted { break; }
match subscriber.wait_for_block_height(wanted).await {
Ok(()) => break,
Err(e) if e.to_string().contains("channel was closed") => {
// transient teardown of the subscription service: back off and retry
tokio::time::sleep(Duration::from_millis(500)).await;
continue;
}
Err(e) => return Err(e),
}
} Prevention
- Always short-circuit on current_block_height() before awaiting a future height.
- Wrap height waits in an overall timeout so service teardown cannot hang callers.
- Treat channel-closure as transient; re-subscribe instead of failing the request.
When it happens
Trigger: Awaiting a block height while the BlockHeightSubscriptionService is stopped/dropped or clears its handler map — node shutdown, service restart, or internal cleanup removing pending handlers.
Common situations: GraphQL subscriptions or internal awaiters pending on future heights during node shutdown or service re-initialization; long waits for heights that are never produced (paused block production) followed by cleanup.
Related errors
- Failed to wait for the required fuel block height: {}
- {error}
- Stop the rollback due to shutdown signal received
- The `message_block_height` is higher than `commit_block_heig
- The count of leaves - messages is zero
AI-assisted analysis of FuelLabs/fuel-core@b9d4d170da (2026-08-16).
Data as JSON: /api/errors/3315956f9486297f.
Report an issue: GitHub.