p2p/discover: clean up forwarder goroutine on waitForNodes success

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.
This commit is contained in:
Csaba Kiraly 2026-05-08 10:10:37 +02:00
parent c3f17e6172
commit a5844b48e4
No known key found for this signature in database

View file

@ -753,6 +753,11 @@ func (tab *Table) deleteNode(n *enode.Node) {
// waitForNodes blocks until the table contains at least n nodes. // waitForNodes blocks until the table contains at least n nodes.
func (tab *Table) waitForNodes(ctx context.Context, n int) error { 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 // 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 // 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. // 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: case <-tab.closeReq:
notifyErr = errClosed notifyErr = errClosed
return return
case <-done:
return
} }
} }
}() }()