From a5844b48e40afc9713285ac59a2c380894a5caef Mon Sep 17 00:00:00 2001 From: Csaba Kiraly Date: Fri, 8 May 2026 10:10:37 +0200 Subject: [PATCH] p2p/discover: clean up forwarder goroutine on waitForNodes success MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The forwarder goroutine spawned by waitForNodes only exits on ctx.Done() or tab.closeReq. On a successful return (count satisfied via getlength()), the goroutine stayed parked until ctx fires or the table closes — a leak for callers that pass long-lived contexts. Add a local done channel, closed via defer, so the goroutine exits when waitForNodes returns regardless of ctx state. --- p2p/discover/table.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/p2p/discover/table.go b/p2p/discover/table.go index 7781b5df6b..8f41f9793e 100644 --- a/p2p/discover/table.go +++ b/p2p/discover/table.go @@ -753,6 +753,11 @@ func (tab *Table) deleteNode(n *enode.Node) { // waitForNodes blocks until the table contains at least n nodes. func (tab *Table) waitForNodes(ctx context.Context, n int) error { + // done lets the forwarder goroutine exit when waitForNodes returns + // successfully (without ctx cancellation or table close). + done := make(chan struct{}) + defer close(done) + // Set up a notification channel that gets unblocked when there was any activity on // the table. Ultimately this reads from the table's nodeFeed, but can't use the feed // directly on the same goroutine that takes Table.mutex, it would deadlock. @@ -777,6 +782,8 @@ func (tab *Table) waitForNodes(ctx context.Context, n int) error { case <-tab.closeReq: notifyErr = errClosed return + case <-done: + return } } }()