Skip to content

WIP: fix(node): bound derivation shutdown wait - #1047

Open
curryxbo wants to merge 3 commits into
mainfrom
fix/derivation-shutdown-cancel-propagation
Open

WIP: fix(node): bound derivation shutdown wait#1047
curryxbo wants to merge 3 commits into
mainfrom
fix/derivation-shutdown-cancel-propagation

Conversation

@curryxbo

@curryxbo curryxbo commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Problem

Derivation.Stop() cancels the context, then blocks on <-d.stop with no bound. The main loop only checks ctx.Done() between polls, so shutdown waits for the in-flight poll to unwind. When L2 is unavailable that poll sits inside RetryableClient's backoff, which is not context-bound and treats a canceled context as retryable, so it runs for GethRetryMaxElapsedTime (30 min). A SIGTERM during an L2 outage held Stop until the supervisor sent SIGKILL.

Changes

Contained to node/derivation/derivation.go:

  • Stop() waits on d.stop with a 30s bound and logs if it expires. Abandoning the wait is safe: the process exits once Stop returns, and the L1 cursor is only persisted after a fully successful poll, so a partially derived batch is redone on restart.
  • fetchRollupDataByTxHash passes d.ctx instead of context.Background(). That client is a raw ethclient.Client with no retry wrapper, so cancellation aborts the call immediately.
  • derive's BlockNumber call keeps context.Background(), now with a comment explaining why: it goes through RetryableClient, where a canceled context is classified retryable, so d.ctx there would spin for the full 30-minute budget instead of returning.

The underlying issue — retryableError not treating context errors as permanent, and the backoff not being bound to the caller's context — is left alone. Fixing it touches every sequencer RPC path and is not needed to bound shutdown.

Test plan

  • go test ./types/... ./derivation/...
  • Devnet: SIGTERM a full node while the L2 EL is stopped, confirm the process exits within the Stop bound instead of hanging

…ancellation

RetryableClient shared one BackOff across all methods and passed it to
backoff.Retry unbound, so a canceled context could not stop a retry loop;
context errors were also classified retryable. A shutdown during an L2
outage therefore held Derivation.Stop for the full 30-minute budget.

Bind the backoff to the caller's context (fresh instance per call, since
BackOff is stateful and these methods run concurrently), treat context
errors as permanent, and bound Stop's wait so shutdown cannot hang on a
poll that is mid-RPC.

Co-authored-by: Cursor <cursoragent@cursor.com>
@curryxbo
curryxbo requested a review from a team as a code owner August 28, 2026 06:14
@curryxbo
curryxbo requested review from twcctop and removed request for a team August 28, 2026 06:14
@coderabbitai

coderabbitai Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 99b4a6d9-58f4-44d3-8607-7b8a2be9a25c

📥 Commits

Reviewing files that changed from the base of the PR and between fbb298a and 9209776.

📒 Files selected for processing (3)
  • node/derivation/derivation.go
  • node/types/retryable_client.go
  • node/types/retryable_client_test.go

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

Derivation shutdown now has a bounded wait and uses its context for selected RPC calls. Retryable RPC operations now create per-call, context-bound backoff policies and do not retry canceled or expired contexts. Tests cover cancellation classification and immediate retry termination.

Changes

Derivation context lifecycle

Layer / File(s) Summary
Derivation shutdown and RPC context propagation
node/derivation/derivation.go
Stop waits up to 30 seconds for the main loop to unwind. TransactionByHash and BlockNumber now use the derivation context.

Context-bound retry policy

Layer / File(s) Summary
Context-bound retry policy and retry call sites
node/types/retryable_client.go, node/types/retryable_client_test.go
RetryableClient creates a fresh context-bound exponential backoff for each operation. All retryable methods use the new policy. Context cancellation and deadline errors are permanent. Tests verify both behaviors.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: 🟡 Moderate · up to 92097

The PR makes RPC retries cancellation-aware and bounds shutdown, but Stop can return while an L2 block operation is still running because the operation may outlive the 30-second shutdown limit. A restart could therefore overlap with L2 state mutation; merge requires explicit owner acceptance or aligning the operation and shutdown lifetimes.

Suggested reviewers: twcctop, dylancai9, tomatoishealthy

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.14% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 7 functions across 3 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the bounded derivation shutdown wait, which is a real and important change in the pull request. It does not mention the related RPC retry and context-cancellation changes, …
Full details: Title check

Explanation

The title clearly describes the bounded derivation shutdown wait, which is a real and important change in the pull request. It does not mention the related RPC retry and context-cancellation changes, but the title does not need to cover every change.

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/derivation-shutdown-cancel-propagation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Classifying context errors as permanent is enough to unblock a canceled
caller: retryableError returning false makes the backoff operation return
nil, so the call exits after one attempt. Binding the backoff to ctx was
a separate robustness fix and is not needed here.

Co-authored-by: Cursor <cursoragent@cursor.com>
@curryxbo curryxbo changed the title fix(node): make RPC retries and derivation shutdown respect context cancellation fix(node): unblock derivation shutdown on a canceled context Aug 28, 2026
Revert the retryableError change; bounding Stop's wait is enough to keep
shutdown from hanging, since the process exits once Stop returns. Document
why derive's BlockNumber call must stay on context.Background().

Co-authored-by: Cursor <cursoragent@cursor.com>
@curryxbo curryxbo changed the title fix(node): unblock derivation shutdown on a canceled context fix(node): bound derivation shutdown wait Aug 28, 2026
@curryxbo curryxbo changed the title fix(node): bound derivation shutdown wait WIP: fix(node): bound derivation shutdown wait Aug 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant