Skip to content

[PR #32] Blocking mpsc::send in WS drain loop stalls listener — missed liquidations #93

@obchain

Description

@obchain

PR: #32 feat/07-block-listener
File: crates/charon-scanner/src/listener.rs — BlockListener::run_once, inside while let Some(header) loop

Problem

if self.tx.send(event).await.is_err() {
    return Ok(());
}

mpsc::Sender::send is async; suspends task until receiver pops a slot. If downstream scanner stalls — even briefly — WS drain loop suspends here. alloy WebSocket does not drain server frames while future pending. After server send-buffer fills, connection dropped, triggering reconnect with exponential backoff. Blocks produced during reconnect never seen.

BSC ~3s per block: 1s scanner stall = one missed block. Liquidation opportunity can appear and be gone within single block.

Impact

Silent missed blocks during downstream congestion. No observable error; reconnect counter increments but root cause (slow consumer) invisible.

Fix

Replace blocking send with non-blocking variant; log drops:

match self.tx.try_send(event) {
    Ok(()) => {}
    Err(mpsc::error::TrySendError::Full(_)) => {
        warn!(chain = %self.name, block = number, "channel full — block dropped");
    }
    Err(mpsc::error::TrySendError::Closed(_)) => return Ok(()),
}

Alternatively send_timeout(event, Duration::from_millis(200)). Invariant: WS drain loop must never block indefinitely.

Metadata

Metadata

Assignees

No one assigned

    Labels

    layer:rustRust crates (core / scanner / protocols / executor / cli)pr-reviewFindings from PR review processpriority:p1-coreCore MVP scopetype:featureNew capability or deliverable

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions