simplify AsyncFilter design

Signed-off-by: Csaba Kiraly <csaba.kiraly@gmail.com>

# Conflicts:
#	eth/backend.go
This commit is contained in:
Csaba Kiraly 2025-04-28 19:25:25 +02:00
parent 835a4e5943
commit a80c579d7f
No known key found for this signature in database
GPG key ID: 0FE274EE8C95166E
2 changed files with 51 additions and 42 deletions

View file

@ -18,6 +18,7 @@
package eth package eth
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"math/big" "math/big"
@ -503,10 +504,13 @@ func (s *Ethereum) setupDiscovery() error {
// Add DHT nodes from discv4. // Add DHT nodes from discv4.
if s.p2pServer.DiscoveryV4() != nil { if s.p2pServer.DiscoveryV4() != nil {
asyncFilter := s.p2pServer.DiscoveryV4().RequestENR iter := s.p2pServer.DiscoveryV4().RandomNodes()
filter := eth.NewNodeFilter(s.blockchain) resolverFunc := func(ctx context.Context, enr *enode.Node) (*enode.Node, error) {
iter := enode.AsyncFilter(s.p2pServer.DiscoveryV4().RandomNodes(), asyncFilter, maxParallelENRRequests) // RequestENR does not yet support context. It will simply time out.
iter = enode.Filter(iter, filter) return s.p2pServer.DiscoveryV4().RequestENR(enr)
}
iter = enode.AsyncFilter(iter, resolverFunc, maxParallelENRRequests)
iter = enode.Filter(iter, eth.NewNodeFilter(s.blockchain))
s.discmix.AddSource(iter) s.discmix.AddSource(iter)
} }

View file

@ -17,6 +17,7 @@
package enode package enode
import ( import (
"context"
"sync" "sync"
"time" "time"
) )
@ -156,50 +157,52 @@ func (f *filterIter) Next() bool {
// the 'check' function returns a (possibly modified) node. // the 'check' function returns a (possibly modified) node.
type AsyncFilterIter struct { type AsyncFilterIter struct {
it Iterator // the iterator to filter it Iterator // the iterator to filter
check func(*Node) (*Node, error) // the blocking check function
slots chan struct{} // the slots for parallel checking slots chan struct{} // the slots for parallel checking
passed chan *Node // channel to collect passed nodes passed chan *Node // channel to collect passed nodes
closed chan struct{} // channel to override passed when closing
buffer *Node // buffer to serve the Node call buffer *Node // buffer to serve the Node call
cancel context.CancelFunc
closeOnce sync.Once
} }
type AsyncFilterFunc func(context.Context, *Node) (*Node, error)
// AsyncFilter creates an iterator which checks nodes in parallel. // AsyncFilter creates an iterator which checks nodes in parallel.
func AsyncFilter(it Iterator, check func(*Node) (*Node, error), workers int) Iterator { func AsyncFilter(it Iterator, check AsyncFilterFunc, workers int) Iterator {
f := &AsyncFilterIter{it, check, make(chan struct{}, workers), make(chan *Node), make(chan struct{}), nil} f := &AsyncFilterIter{
it: it,
// create slots slots: make(chan struct{}, workers+1),
passed: make(chan *Node),
}
for range cap(f.slots) { for range cap(f.slots) {
f.slots <- struct{}{} f.slots <- struct{}{}
} }
ctx, cancel := context.WithCancel(context.Background())
f.cancel = cancel
go func() { go func() {
select {
case <-ctx.Done():
return
case <-f.slots:
}
// read from the iterator and start checking nodes in parallel // read from the iterator and start checking nodes in parallel
// when a node is checked, it will be sent to the passed channel // when a node is checked, it will be sent to the passed channel
// and the slot will be released // and the slot will be released
for range f.slots { for f.it.Next() {
if f.it.Next() { n := f.it.Node()
if n := f.it.Node(); n != nil { <-f.slots
// check the node async, in a separate goroutine // check the node async, in a separate goroutine
go func() { go func() {
if nn, err := f.check(n); err == nil { if nn, err := check(ctx, n); err == nil {
select { select {
case f.passed <- nn: case f.passed <- nn:
case <-f.closed: // bale out if downstream is already closed and not calling Next case <-ctx.Done(): // bale out if downstream is already closed and not calling Next
} }
} }
f.slots <- struct{}{} f.slots <- struct{}{}
}() }()
} else {
// this is not supposed to happen
f.slots <- struct{}{}
break
} }
} else {
// the iterator has ended // the iterator has ended
f.slots <- struct{}{} f.slots <- struct{}{}
break
}
}
}() }()
return f return f
@ -218,13 +221,15 @@ func (f *AsyncFilterIter) Node() *Node {
// Close ends the iterator, also closing the wrapped iterator. // Close ends the iterator, also closing the wrapped iterator.
func (f *AsyncFilterIter) Close() { func (f *AsyncFilterIter) Close() {
f.closeOnce.Do(func() {
f.it.Close() f.it.Close()
close(f.closed) // override the passed channel f.cancel()
for range cap(f.slots) { for range cap(f.slots) {
<-f.slots <-f.slots
} }
close(f.slots) close(f.slots)
close(f.passed) close(f.passed)
})
} }
// FairMix aggregates multiple node iterators. The mixer itself is an iterator which ends // FairMix aggregates multiple node iterators. The mixer itself is an iterator which ends