op-batcher: bound every Espresso network call and drop the streamer on stop - #500
op-batcher: bound every Espresso network call and drop the streamer on stop#500philippecamacho wants to merge 2 commits into
Conversation
…n stop Three wedge modes from the #459 review round, all the same family: a hung network call with no deadline parks a component forever. Submit/verify workers: the Espresso SDK issues plain HTTP requests with no client-side timeout, and the workers called it with their long-lived loop contexts, so one black-holed connection consumed a worker permanently - with all four wedged, submission stayed stopped even after the endpoint recovered. The submitter's client dependency is now the three-method EspressoSubmitClient (narrowed from the SDK's full interface, whose streaming endpoints a per-call deadline would break), and the driver hands it a boundedEspressoClient that wraps every call with the network timeout. This covers the height tracker too. Loading loop: Peek retries undecided batches' L1-backed validity checks (a contract call plus a header fetch) on the loop's shutdown context, so a hung L1 RPC silently stalled frame publication for good. Peek now gets the same per-call bound as every other raw RPC; on expiry the batch stays undecided and is retried next tick. Stop/start: l.espressoStreamer was never nil'ed, so the next StartBatchSubmitting ran clearState against the stale streamer and its re-anchor gate retried every 5s with no deadline while holding the start mutex - which the stop that could cancel it also needs. Against a resyncing op-node that deadlocks SIGTERM forever, the same start-mutex-hang pattern already fixed for waitForLocalSafeHead and registration. StopBatchSubmitting and rollbackFailedStart now drop the stopped streamer, so every start reaches clearState streamer-free and the gate only ever runs from the loading loop, whose context a stop can cancel without the mutex. Tests pin the wrapper's per-call deadline on all three methods (blocking fake with no parent deadline) and the streamer drop on both teardown paths. The Peek bound is a context wrap with no seam to observe it through a unit test; it rides on the streamer's own retry semantics. Closes #493. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…st fakes Cleanup pass over the deadline/teardown change: - teardownEspressoStreamer now owns the Stop+nil sequence and the single canonical statement of why a dead run's streamer must not survive into the next start; StopBatchSubmitting and rollbackFailedStart call it, so a future teardown path cannot forget the nil, and the fork-specific block in upstream-owned driver.go shrinks to one line. - Comment dedup around it: espressoReanchorTarget's doc and both lifecycle tests now point at the helper instead of restating the mutex-deadlock argument; networkTimeoutCtx's doc no longer maintains a caller registry; the wrapper construction site lets the constructor name speak. - The interface narrowing made the helpers-file fakes' full-SDK surface dead weight: ~150 lines of ErrNotImplemented stubs and proxy methods for endpoints nothing calls, plus compile-time assertions that re-coupled the tests to SDK interface growth. All three fakes now implement batcher.EspressoSubmitClient directly. - The three-case deadline test drops its map/subtest scaffolding for three direct assertions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The mechanism checks out. I put the real SDK behind a listener that accepts TCP and never answers, called it through a 500ms bound on a deadline-free parent, and every method came back on time: both the GETs ( It's where the bound sits that I'd change. I also don't think this closes #493. That wedge isn't the streamer, it's Smaller stuff:
Two I chased and left: a re-sent submit is deduped by |
I agree with the problem. Now the solution proposed is likely not enough as if the first node is failing it will eat up the whole time Every HTTP path in sdks/go/client uses http.DefaultClient, which has Timeout: 0: client.go:75 (NewClientFromOptions) and client.go:88 (NewClient) WDYT @jjeangal? |
@philippecamacho agreed, the SDK is the right place. One addition though, a client timeout alone doesn't fix what @palango raised. |
Closes #493. Stacked on #459 (
espresso/batcher).Problem
Three wedge modes, all the same family: a hung network call with no deadline parks a component forever.
Peekretries undecided batches' L1-backed validity checks (a contract call plus a header fetch) on the loop's shutdown context, so a hung L1 RPC silently stalled frame publication with no error logs.l.espressoStreamerwas never nil'ed, so the nextStartBatchSubmittingranclearStateagainst the stale streamer, whose re-anchor gate retries every 5s with no deadline while holding the start mutex — the very mutex the stop that could cancel it needs. Against a resyncing op-node, a later SIGTERM blocked forever (the same start-mutex-hang pattern already fixed forwaitForLocalSafeHeadand registration).Fix
Per the issue's suggestion, the SDK timeout is enforced once at a client-wrapper seam rather than per call site:
EspressoSubmitClient(the SDK's full interface includes streaming endpoints, which a per-call deadline would break), and the driver hands it aboundedEspressoClientwrapping every call withNetworkTimeout. This covers the block-height tracker too.Peekgets the same per-call bound as every other raw RPC on this path (via the existingnetworkTimeoutCtx); on expiry the batch stays undecided and is retried next tick, and the streamer's own warn logs now surface instead of silence.StopBatchSubmittingandrollbackFailedStartdrop the stopped streamer, so every start reachesclearStatestreamer-free and the no-deadline re-anchor gate only ever runs from the loading loop, whose context a stop can cancel without taking the mutex.Tests
TestBoundedEspressoClientAppliesDeadline: a blocking fake (models the timeout-less SDK on a black-holed connection) called with a deadline-free parent context — each of the three wrapped methods must returnDeadlineExceededpromptly.TestStopBatchSubmittingDropsStreamer/TestRollbackFailedStartDropsStreamer: both teardown paths must leaveespressoStreamernil (a zero-valueStreamerisStop()-safe, which makes the lifecycle directly unit-testable).Peekbound is a context wrap with no seam a unit test can observe; it rides on the streamer's existing undecided-retry semantics.go vetand the fullop-batcher/batchersuite pass.🤖 Generated with Claude Code