fix: make a fatal ASM worker exit shut the runner down - #229
Merged
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests.
... and 2 files with indirect coverage changes 🚀 New features to boost your workflow:
|
|
Commit: aef46ad
|
prajwolrg
marked this pull request as ready for review
August 16, 2026 15:54
🔒 AI Security Review (claude-opus-5)✅ No security issues found. |
prajwolrg
force-pushed
the
fix/fatal-asm-worker-exit
branch
from
August 18, 2026 10:38
1ed530a to
f4d6312
Compare
🔒 AI Security Review (claude-opus-5)✅ No security issues found. |
prajwolrg
force-pushed
the
fix/fatal-asm-worker-exit
branch
from
August 19, 2026 02:46
f4d6312 to
101adf7
Compare
🔒 AI Security Review (claude-opus-5)✅ No security issues found. |
prajwolrg
force-pushed
the
fix/fatal-asm-worker-exit
branch
2 times, most recently
from
August 19, 2026 02:53
b13e6a3 to
36783f3
Compare
🔒 AI Security Review (claude-opus-5)✅ No security issues found. |
prajwolrg
force-pushed
the
fix/fatal-asm-worker-exit
branch
from
August 19, 2026 03:00
355607b to
67d12cf
Compare
🔒 AI Security Review (claude-opus-5)✅ No security issues found. |
A sync failure is terminal for the worker, but it was reported as Ok(Response::ShouldExit). The service framework treats that as a clean exit, and strata-tasks only raises a TaskError for Err or a panic, so nothing signalled the task manager and the process stayed up. That left a zombie node: the block watcher kept submitting blocks nobody would process, the RPC kept serving stale state, and no supervisor had a reason to restart the binary. Return the error instead so the critical task fails, the shutdown signal reaches every other task, and the runner exits non-zero. The caller still receives the typed WorkerError through the completion. WorkerError is not Clone, so the task exits with a rendering of it rather than the value. SECFIND-339
`Subscription::recv` was documented as returning `None` once the worker shuts down. That never happens. Closure is inferred from every sender being dropped, and the worker handle holds a clone of the registry, so the senders outlive the emitting worker. Nothing is broken by this today. Every consumer is a service worker, and the framework races its message loop against the shutdown guard, so a dead worker takes the process down and consumers exit on that signal. But the docs invited a bare `recv` loop that would park forever, so say what the type actually does. SECFIND-341
The comments explaining the fatal-error path said a sync error is fatal for "the runner", and that an `Err` shuts the process down. The crate knows neither. It knows it is spawned via `spawn_critical`, so an `Err` is reported as a critical-task failure, but what the host does with that is the host's policy. Ours panics; another need not. Say only what the crate controls: the error is terminal, so report it as one, because a clean exit is indistinguishable from finishing the work.
prajwolrg
force-pushed
the
fix/fatal-asm-worker-exit
branch
from
August 19, 2026 05:59
67d12cf to
d2b784d
Compare
🔒 AI Security Review (claude-opus-5)✅ No security issues found. |
bewakes
approved these changes
Aug 19, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
A fatal ASM sync failure used to leave the runner alive but permanently stuck. The worker reported the failure as
Ok(Response::ShouldExit), which the service framework treats as a clean exit, andstrata-tasksonly raises aTaskErrorfor anError a panic. So nothing signalled the task manager, the process stayed up, the block watcher kept submitting blocks nobody would process, the RPC kept serving stale state, and no supervisor had a reason to restart the binary.Returning the error instead makes the fatal path actually fatal. The runner exits non-zero and gets restarted.
Fixes SECFIND-339 and SECFIND-341.
Type of Change
Notes to Reviewers
The fix is three lines in
crates/worker/src/service.rs. Everything else in this PR is documentation.Fixed at the worker, not the watcher. SECFIND-339 proposes propagating the error out of the block watcher instead. Both routes end at a
TaskError, but the watcher only learns the worker is dead on its next submission, which is the next ZMQ block. That is potentially ten minutes of a node that is up, answers RPC, and is silently not advancing. Fixing it at the worker makes shutdown synchronous with the failure, and keeps the "my exit is terminal" contract with the component that knows it rather than re-implementing it in every caller.Nothing else needs to change to cooperate with it. An earlier version of this PR also made the watcher fail its own task on a submission error. That turned out to be redundant: once the worker returns
Err, the shutdown signal reaches the watcher through the guard it already selects on, so it stops on its own after at most one extra log line. Worse, the watcher change created a problem of its own. Shutdown reaches the worker before the watcher, and the worker drops the completion for an input it has already dequeued, so an ordinary Ctrl-C during a submission comes back asWorkerExitedand would have failed the watcher's critical task on every clean stop. Fixing that needed a second commit to un-break what the first one broke. Both are dropped.The two findings are related but the second one is a docs bug. SECFIND-341 is that subscriptions never close, so followers park forever. They only park forever because the process outlives the worker, which is SECFIND-339. Closing the sender registry does not fix the availability problem on its own: it turns "Moho parks" into "Moho's stream ends and its task exits cleanly", and a cleanly exiting critical task still does not trigger shutdown. That is a quieter zombie, not a restart.
What is left once SECFIND-339 is fixed is that
Subscription::recvdocumented a contract the type does not honour. It claimed to returnNonewhen the worker shuts down. It does not, because the worker handle holds a clone of the registry for the whole process, so the senders outlive the emitting worker. No consumer is affected today, since both are service workers whose message loop is raced against the shutdown guard, but the docs invited a barerecvloop that would park forever. The second commit corrects the contract rather than changing the behaviour. Closing the registry explicitly would be the behavioural fix, but with SECFIND-339 fixed there is nothing left for it to prevent, so there is no reason to carry the extra state.One judgement call worth flagging.
WorkerErroris notCloneand the caller must keep receiving the typed error through the completion channel, so the error the task exits with is a rendering of it rather than the value itself. The full typed error still reaches the caller, and the worker logs it at the point of failure. MakingWorkerErrorcloneable to avoid this seemed a worse trade than the lost source chain in one log line.Out of scope, worth a follow-up. The sync worker loop in
strata-serviceonly checks its shutdown guard afterrecv_next()returns, so the ASM worker thread does not wake promptly on a global shutdown. It waits for the nextsubmit_blockor the runner'sSHUTDOWN_TIMEOUT. The process still exits, just not instantly. That is an upstreamstrata-commonissue.Checklist
Related Issues
SECFIND-339, SECFIND-341