fix(core/txpool): fix Sync deadlock in pool loop #28837 - #2542
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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. Comment |
5c58063 to
25aaa21
Compare
There was a problem hiding this comment.
Pull request overview
Fixes txpool reset synchronization races by aligning head-event subscription and waiter signaling with upstream geth.
Changes:
- Subscribes to chain-head events synchronously during pool creation.
- Guarantees delivery of reset completion notifications.
- Adds regression tests for synchronization and head-event handling.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
core/txpool/txpool.go |
Updates subscription lifecycle and reset waiter signaling. |
core/txpool/txpool_sync_test.go |
Adds synchronization and head-event regression tests. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
25aaa21 to
e6b1581
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
Suppressed comments (1)
core/txpool/txpool_sync_test.go:91
- Calling
Syncsynchronously means a regression recreates the original behavior: this test stalls the entire package until the global CI timeout (45 minutes in the referenced run) instead of reporting a prompt failure. Run the call in a goroutine and select on its result with a bounded timeout so the deadlock test fails quickly and diagnostically.
for i := 0; i < 100; i++ {
if err := pool.Sync(); err != nil {
60eb82c to
27dc451
Compare
…tion ethereum#28837 Sync() could block forever: the resetDone branch notified the sync waiter with a non-blocking send and then unconditionally cleared it. When Sync() had not yet reached its receive (a scheduling gap after it sent its waiter), the notification was dropped, leaving the waiter never signaled. Restore the blocking send used upstream geth, which cannot lose the notification, and do the same for the loop-termination notification so a pool closed while Sync() is pending also signals the waiter. Add TestSyncCompletes which exercises repeated Sync() calls and would hang under -cover if the non-blocking notification regressed.
27dc451 to
34aadee
Compare
Summary
Fixes a
pool.Sync()deadlock that surfaced as a 45-minute test timeout oncore/txpool/locals.Ref: ethereum#28837
Symptoms
CI hung until the 45-minute test timeout, with
running tests: TestTrackAllSameNonceReplacement. The goroutine dump showed the test goroutine blocked atTxPool.Sync()'s<-waiterwhileTxPool.loopidled in select with no reset goroutine running —Sync()had sent its sync request and the loop had received it, but the waiter was never signaled back.Ref: https://github.com/XinFinOrg/XDPoSChain/actions/runs/32908385602/job/97997298962
Root cause
Upstream geth notifies the sync waiter in the
resetDonebranch ofTxPool.loopwith a blocking send:This fork's commit
a9be217e6 (#2132)changed it to a non-blocking send followed by an unconditional clear:Deadlock chain:
Sync()sends its waiter overp.sync; the loop receives it and setsresetWaiter/resetForced=true;Sync()then runsreturn <-waiter— a scheduling gap exists between these two steps;Sync()reaches<-waiter,resetWaiter <- niltakes thedefaultbranch (no receiver yet), the notification is dropped, andresetWaiteris unconditionally cleared;Sync()'s waiter never receives a value → blocked forever.Why only CI hit it: coverage mode inserts counters at every statement, widening the descheduling window between sending the waiter and starting to receive; parallel high load worsens it. Plain local runs almost never trigger it.
Fix
Restore the blocking send used upstream, which cannot lose the notification, and apply the same to the loop-termination notification so a pool closed while
Sync()is pending also signals the waiter. AddTestSyncCompletes, which exercises repeatedSync()calls and hangs under-coverif the non-blocking notification regresses.Verification
core/txpool/locals: 100 consecutive runs under-coverall pass (previously timed out at the same parameters);core/txpool: full package passes;TestSyncCompletespasses 100 runs under-coverand-race;TestSyncCompleteshangs and times out under-cover, confirming it catches the deadlock.Compatibility
Internal txpool concurrency control only; no protocol, RPC, or state-transition changes.