fix(rpc): exit newPendingTransactions goroutine when event channel is closed - #4641
Open
artemrootman wants to merge 2 commits into
Open
fix(rpc): exit newPendingTransactions goroutine when event channel is closed#4641artemrootman wants to merge 2 commits into
artemrootman wants to merge 2 commits into
Conversation
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
pubSubAPI.subscribePendingTransactions(rpc/websockets.go) reads from the subscription event channel without checking whether the channel is still open:When the client sends
eth_unsubscribeor the websocket connection drops, the websockets server calls theUnsubscribeFuncreturned bymemEventBus.Subscribe, which closes the per-subscriber channel (txsCh) but never closessub.Err(). A receive from a closed channel returns the zero value immediately, so the goroutine falls into a tight loop:ev.Data == nil, the type assertion fails, it formats a Debug log line andcontinues, forever, with no sleep. Each leaked goroutine burns a full CPU core, and since the log level is Debug nothing shows up in the logs.subscribeNewHeadsandsubscribeLogsalready handle this correctly withcase event, ok := <-ch: if !ok { return }; this PR applies the same check to the pending-transactions handler.Impact observed in production
On a mainnet full node (
v36.0.6, 40 CPU quota) serving an RPC load balancer that opensnewPendingTransactionsover websocket and reconnects periodically, ~100 such goroutines accumulated over two days of uptime. The node was pinned at 100% of its CPU quota and fell behind the chain head by ~800 blocks because consensus/block execution was starved.CPU profile (15 s,
go tool pprof -top):Goroutine dump (
/debug/pprof/goroutine?debug=1) showed 97 goroutines parked insubscribePendingTransactions.func1atwebsockets.go:740while only 5 websocket connections were open.Restarting the node clears the goroutines, but they accumulate again with every client reconnect.
How Has This Been Tested?
go build ./rpc/andgo vet ./rpc/pass (go1.23.8).Reasoning matches the existing
newHeads/logshandlers, which use the identicalokcheck and do not leak under the same reconnect pattern.Tested CCTX in localnet
Tested in development environment
Go unit tests
Go integration tests
Tested via GitHub Actions
Note
Low Risk
Small, localized websocket subscription lifecycle fix aligned with existing handlers; reduces resource exhaustion risk without changing subscription behavior for open connections.
Overview
Fixes a goroutine and CPU leak in the
newPendingTransactionswebsocket subscription by treating a closed event channel as a clean shutdown.When clients unsubscribe or disconnect, the pending-tx event channel is closed but the handler kept receiving zero values and spinning in a tight loop (failed type assertions + debug logging). The change mirrors
subscribeNewHeadsandsubscribeLogs: useev, ok := <-txsChandreturnwhen!okso the goroutine exits instead of leaking.Reviewed by Cursor Bugbot for commit 948354d. Configure here.
Greptile Summary
This PR fixes a busy-loop and goroutine leak in the pending-transactions websocket subscription handler.
Confidence Score: 5/5
The PR appears safe to merge and correctly prevents the pending-transactions goroutine from spinning after its event channel closes.
The new receive-status check exits only after the subscription event channel has closed, while existing websocket lifecycle code continues to own unsubscription and connection cleanup; no actionable regressions were identified.
Important Files Changed
Reviews (1): Last reviewed commit: "fix(rpc): exit newPendingTransactions go..." | Re-trigger Greptile