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.
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 count of leaves - messages is zero
- The `message_block_height` is higher than…
AI-assisted analysis of FuelLabs/fuel-core@b9d4d170da (2026-08-16).
Data as JSON: /api/errors/3315956f9486297f.
Report an issue: GitHub.
Appendix: 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)