p2p/enode: sync BufferIter Close and Next

When Close was called while Next was active, and there was a race
on the closed channel. If Close finished before closed was received,
This happened:

goroutine 22716 [chan send, 5 minutes]:
github.com/ethereum/go-ethereum/p2p/enode.NewBufferIter.func1()
	github.com/ethereum/go-ethereum/p2p/enode/iter.go:219 +0x77
created by github.com/ethereum/go-ethereum/p2p/enode.NewBufferIter in goroutine 1
	github.com/ethereum/go-ethereum/p2p/enode/iter.go:216 +0xd8

goroutine 22714 [chan receive (nil chan), 1 minutes]:
github.com/ethereum/go-ethereum/p2p/enode.(*BufferIter).Next(0xc00505fea0)
	github.com/ethereum/go-ethereum/p2p/enode/iter.go:226 +0x2d
github.com/ethereum/go-ethereum/p2p/enode.AsyncFilter.func1()
	github.com/ethereum/go-ethereum/p2p/enode/iter.go:151 +0x5f
created by github.com/ethereum/go-ethereum/p2p/enode.AsyncFilter in goroutine 1
	github.com/ethereum/go-ethereum/p2p/enode/iter.go:146 +0x156

Signed-off-by: Csaba Kiraly <csaba.kiraly@gmail.com>
This commit is contained in:
Csaba Kiraly 2025-04-24 18:25:35 +02:00
parent 83e5556eb4
commit 0608eb20e9
No known key found for this signature in database
GPG key ID: 0FE274EE8C95166E

View file

@ -239,6 +239,7 @@ type BufferIter struct {
buffer chan *Node
head *Node
closed chan struct{}
mu sync.Mutex
}
// NewBufferIter creates a new pre-fetch buffer of a given size.
@ -266,6 +267,9 @@ func NewBufferIter(it Iterator, size int) *BufferIter {
}
func (b *BufferIter) Next() bool {
b.mu.Lock()
defer b.mu.Unlock()
select {
case b.head = <-b.buffer:
case <-b.closed:
@ -275,6 +279,8 @@ func (b *BufferIter) Next() bool {
}
func (b *BufferIter) Node() *Node {
b.mu.Lock()
defer b.mu.Unlock()
return b.head
}
@ -282,6 +288,9 @@ func (b *BufferIter) Close() {
// Close the wrapped iterator first.
b.it.Close()
close(b.closed)
// Wait for Next to terminate, then drain the buffer.
b.mu.Lock()
defer b.mu.Unlock()
for range b.buffer {
}
b.buffer = nil