Refs #42
PR: feat(cli): wire scanner → router → builder → simulator pipeline (feat/17-cli-e2e-pipeline)
Commit: latest on feat/17-cli-e2e-pipeline
File: crates/charon-cli/src/main.rs, spawn loop (lines ~169-174)
Problem:
The spawn loop discards the JoinHandle returned by tokio::spawn:
tokio::spawn(async move {
if let Err(err) = listener.run().await {
warn!(chain = %name, error = ?err, "listener terminated");
}
});
// JoinHandle dropped immediately
A panic inside listener.run() is caught by the tokio runtime as a JoinError on the discarded handle. The warn! closure only fires on an Err return — not on panic. The sender for that chain is dropped, so no further block events arrive from that chain, but the select loop and pipeline continue indefinitely with no error and no recovery.
This was flagged in PR #32 review (finding 4) and has not been resolved across PR #33, #34, or #42. The PR now has more downstream stages that depend on the block stream; a silently-dead listener means the pipeline stops processing without triggering shutdown or alerting.
Impact: A listener panic produces a silent zombie pipeline — operator sees no error, bot processes no blocks, no liquidations fire.
Fix: Use JoinSet to track all spawned listener tasks. In the select loop, poll join_set.join_next() alongside rx.recv() and ctrl_c. On unexpected task completion (panic or unexpected Ok), log error! and trigger controlled shutdown.
Refs #42
PR: feat(cli): wire scanner → router → builder → simulator pipeline (feat/17-cli-e2e-pipeline)
Commit: latest on feat/17-cli-e2e-pipeline
File: crates/charon-cli/src/main.rs, spawn loop (lines ~169-174)
Problem:
The spawn loop discards the JoinHandle returned by tokio::spawn:
A panic inside listener.run() is caught by the tokio runtime as a JoinError on the discarded handle. The warn! closure only fires on an Err return — not on panic. The sender for that chain is dropped, so no further block events arrive from that chain, but the select loop and pipeline continue indefinitely with no error and no recovery.
This was flagged in PR #32 review (finding 4) and has not been resolved across PR #33, #34, or #42. The PR now has more downstream stages that depend on the block stream; a silently-dead listener means the pipeline stops processing without triggering shutdown or alerting.
Impact: A listener panic produces a silent zombie pipeline — operator sees no error, bot processes no blocks, no liquidations fire.
Fix: Use JoinSet to track all spawned listener tasks. In the select loop, poll join_set.join_next() alongside rx.recv() and ctrl_c. On unexpected task completion (panic or unexpected Ok), log error! and trigger controlled shutdown.