mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
p2p/discover: new iterator protocol, make iterator closable
This commit is contained in:
parent
aeffde1dc4
commit
6c3de2ea50
2 changed files with 47 additions and 23 deletions
|
|
@ -58,13 +58,6 @@ type ReadPacket struct {
|
||||||
Addr *net.UDPAddr
|
Addr *net.UDPAddr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iterator is an iterator over nodes.
|
|
||||||
type Iterator interface {
|
|
||||||
// NextNode returns the next node if one could be discovered before the
|
|
||||||
// passed context was canceled.
|
|
||||||
NextNode(context.Context) *enode.Node
|
|
||||||
}
|
|
||||||
|
|
||||||
type lookupFunc func(func(*enode.Node))
|
type lookupFunc func(func(*enode.Node))
|
||||||
|
|
||||||
// lookupWalker performs recursive lookups, walking the DHT.
|
// lookupWalker performs recursive lookups, walking the DHT.
|
||||||
|
|
@ -72,7 +65,8 @@ type lookupFunc func(func(*enode.Node))
|
||||||
type lookupWalker struct {
|
type lookupWalker struct {
|
||||||
lookup lookupFunc
|
lookup lookupFunc
|
||||||
|
|
||||||
newIterCh chan *lookupIterator
|
newIterCh chan *Iterator
|
||||||
|
delIterCh chan *Iterator
|
||||||
triggerCh chan struct{}
|
triggerCh chan struct{}
|
||||||
closeCh chan struct{}
|
closeCh chan struct{}
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
|
|
@ -81,7 +75,8 @@ type lookupWalker struct {
|
||||||
func newLookupWalker(fn lookupFunc) *lookupWalker {
|
func newLookupWalker(fn lookupFunc) *lookupWalker {
|
||||||
w := &lookupWalker{
|
w := &lookupWalker{
|
||||||
lookup: fn,
|
lookup: fn,
|
||||||
newIterCh: make(chan *lookupIterator),
|
newIterCh: make(chan *Iterator),
|
||||||
|
delIterCh: make(chan *Iterator),
|
||||||
triggerCh: make(chan struct{}),
|
triggerCh: make(chan struct{}),
|
||||||
closeCh: make(chan struct{}),
|
closeCh: make(chan struct{}),
|
||||||
}
|
}
|
||||||
|
|
@ -97,7 +92,7 @@ func (w *lookupWalker) close() {
|
||||||
|
|
||||||
func (w *lookupWalker) loop() {
|
func (w *lookupWalker) loop() {
|
||||||
var (
|
var (
|
||||||
iters = make(map[*lookupIterator]struct{})
|
iters = make(map[*Iterator]struct{})
|
||||||
foundNode = make(chan *enode.Node)
|
foundNode = make(chan *enode.Node)
|
||||||
lookupDone = make(chan struct{}, 1)
|
lookupDone = make(chan struct{}, 1)
|
||||||
trigger = w.triggerCh
|
trigger = w.triggerCh
|
||||||
|
|
@ -107,6 +102,9 @@ func (w *lookupWalker) loop() {
|
||||||
case it := <-w.newIterCh:
|
case it := <-w.newIterCh:
|
||||||
iters[it] = struct{}{}
|
iters[it] = struct{}{}
|
||||||
|
|
||||||
|
case it := <-w.delIterCh:
|
||||||
|
delete(iters, it)
|
||||||
|
|
||||||
case <-trigger:
|
case <-trigger:
|
||||||
trigger = nil // stop listening to trigger until lookupDone
|
trigger = nil // stop listening to trigger until lookupDone
|
||||||
go w.runLookup(foundNode, lookupDone)
|
go w.runLookup(foundNode, lookupDone)
|
||||||
|
|
@ -120,6 +118,9 @@ func (w *lookupWalker) loop() {
|
||||||
}
|
}
|
||||||
|
|
||||||
case <-w.closeCh:
|
case <-w.closeCh:
|
||||||
|
for it := range iters {
|
||||||
|
close(it.buf)
|
||||||
|
}
|
||||||
w.wg.Done()
|
w.wg.Done()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -136,39 +137,62 @@ func (w *lookupWalker) runLookup(nodes chan<- *enode.Node, done chan struct{}) {
|
||||||
done <- struct{}{}
|
done <- struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type lookupIterator struct {
|
// Iterator is a sequence of discovered nodes.
|
||||||
w *lookupWalker
|
type Iterator struct {
|
||||||
buf chan *enode.Node
|
w *lookupWalker
|
||||||
|
buf chan *enode.Node
|
||||||
|
closed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
const lookupIteratorBuffer = 100
|
const lookupIteratorBuffer = 100
|
||||||
|
|
||||||
func (w *lookupWalker) newIterator() Iterator {
|
func (w *lookupWalker) newIterator() *Iterator {
|
||||||
it := &lookupIterator{w, make(chan *enode.Node, lookupIteratorBuffer)}
|
it := &Iterator{w, make(chan *enode.Node, lookupIteratorBuffer), false}
|
||||||
select {
|
select {
|
||||||
case w.newIterCh <- it:
|
case w.newIterCh <- it:
|
||||||
case <-w.closeCh:
|
case <-w.closeCh:
|
||||||
|
it.closed = true
|
||||||
|
close(it.buf)
|
||||||
}
|
}
|
||||||
return it
|
return it
|
||||||
}
|
}
|
||||||
|
|
||||||
// NextNode gets the next node from the buffer.
|
// NextNode retrieves the next node if one could be discovered before the passed context
|
||||||
// This keeps triggering new lookups until a node is delivered.
|
// was canceled. This triggers a lookup operation if none is running. The isLive return
|
||||||
func (it *lookupIterator) NextNode(ctx context.Context) *enode.Node {
|
// value says whether the iterator is still open. NextNode returns (nil, false) after Close
|
||||||
|
// has been called.
|
||||||
|
//
|
||||||
|
// NextNode is not safe for concurrent use.
|
||||||
|
func (it *Iterator) NextNode(ctx context.Context) (n *enode.Node, isLive bool) {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case it.w.triggerCh <- struct{}{}:
|
case it.w.triggerCh <- struct{}{}:
|
||||||
// lookup triggered
|
// lookup triggered
|
||||||
case n := <-it.buf:
|
case <-it.w.closeCh:
|
||||||
return n
|
it.closed = true
|
||||||
|
return nil, false
|
||||||
|
case n, ok := <-it.buf:
|
||||||
|
if !ok {
|
||||||
|
it.closed = true
|
||||||
|
}
|
||||||
|
return n, it.closed
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return nil
|
return nil, it.closed // TODO: should be permanently closed if channel is closed once.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close ends the iterator. This can be called concurrently with NextNode.
|
||||||
|
func (it *Iterator) Close() {
|
||||||
|
select {
|
||||||
|
case it.w.delIterCh <- it:
|
||||||
|
close(it.buf)
|
||||||
|
case <-it.w.closeCh:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// deliver sends n to the iterator buffer.
|
// deliver sends n to the iterator buffer.
|
||||||
func (it *lookupIterator) deliver(n *enode.Node) {
|
func (it *Iterator) deliver(n *enode.Node) {
|
||||||
// We don't want deliver to block and replacing stale results is OK if they're not
|
// We don't want deliver to block and replacing stale results is OK if they're not
|
||||||
// being read fast enough. Check whether the buffer is full and enable the select case
|
// being read fast enough. Check whether the buffer is full and enable the select case
|
||||||
// which removes an element if so. This doesn't race because deliver is only called by
|
// which removes an element if so. This doesn't race because deliver is only called by
|
||||||
|
|
|
||||||
|
|
@ -304,7 +304,7 @@ func (t *UDPv4) Close() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// RandomNodes is an iterator yielding nodes from a random walk of the DHT.
|
// RandomNodes is an iterator yielding nodes from a random walk of the DHT.
|
||||||
func (t *UDPv4) RandomNodes() Iterator {
|
func (t *UDPv4) RandomNodes() *Iterator {
|
||||||
return t.randomWalk.newIterator()
|
return t.randomWalk.newIterator()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue