p2p/discover: integrate filter with random walk iterator

This commit is contained in:
Felix Lange 2019-07-15 13:14:45 +02:00
parent 270adea13b
commit ddbab273bb
3 changed files with 84 additions and 53 deletions

View file

@ -21,6 +21,7 @@ import (
"math/rand" "math/rand"
"net" "net"
"sync" "sync"
"sync/atomic"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enode"
@ -70,22 +71,20 @@ type lookupWalker struct {
closeCh chan struct{} closeCh chan struct{}
wg sync.WaitGroup wg sync.WaitGroup
lookup lookupFunc lookup lookupFunc
lookupDone chan struct{} lookupDone chan struct{}
foundNode chan *enode.Node liveItersVal atomic.Value // []*lookupIterator
iters map[*lookupIterator]struct{}
} }
func newLookupWalker(fn lookupFunc) *lookupWalker { func newLookupWalker(fn lookupFunc) *lookupWalker {
w := &lookupWalker{ w := &lookupWalker{
lookup: fn, lookup: fn,
iters: make(map[*lookupIterator]struct{}),
newIterCh: make(chan *lookupIterator), newIterCh: make(chan *lookupIterator),
delIterCh: make(chan *lookupIterator), delIterCh: make(chan *lookupIterator),
triggerCh: make(chan struct{}), triggerCh: make(chan struct{}),
closeCh: make(chan struct{}), closeCh: make(chan struct{}),
foundNode: make(chan *enode.Node),
} }
w.setLiveIters(nil)
w.wg.Add(1) w.wg.Add(1)
go w.loop() go w.loop()
return w return w
@ -96,34 +95,39 @@ func (w *lookupWalker) close() {
w.wg.Wait() w.wg.Wait()
} }
// loop schedules lookups. It ensures a lookup is running while
// any live iterator needs more nodes.
func (w *lookupWalker) loop() { func (w *lookupWalker) loop() {
var lookupDone chan struct{}
iters := make(map[*lookupIterator]struct{})
for { for {
if lookupDone == nil && anyIterNeedsNodes(iters) {
lookupDone = make(chan struct{})
go w.runLookup(lookupDone)
}
select { select {
case it := <-w.newIterCh: case it := <-w.newIterCh:
w.iters[it] = struct{}{} iters[it] = struct{}{}
w.startNewLookup() w.setLiveIters(iters)
case it := <-w.delIterCh: case it := <-w.delIterCh:
delete(w.iters, it) delete(iters, it)
w.setLiveIters(iters)
case <-w.triggerCh: case <-w.triggerCh:
w.startNewLookup()
case <-w.lookupDone: case <-lookupDone:
w.lookupDone = nil lookupDone = nil
w.startNewLookup()
case n := <-w.foundNode:
for it := range w.iters {
it.deliver(n)
}
case <-w.closeCh: case <-w.closeCh:
for it := range w.iters { w.setLiveIters(nil)
for it := range iters {
it.close() it.close()
} }
if w.lookupDone != nil { if lookupDone != nil {
<-w.lookupDone <-lookupDone
} }
w.wg.Done() w.wg.Done()
return return
@ -131,44 +135,62 @@ func (w *lookupWalker) loop() {
} }
} }
func (w *lookupWalker) startNewLookup() { func anyIterNeedsNodes(iters map[*lookupIterator]struct{}) bool {
if w.lookupDone != nil { for it := range iters {
return // already running
}
for it := range w.iters {
if it.needsNodes() { if it.needsNodes() {
w.lookupDone = make(chan struct{}) return true
go w.runLookup()
return
} }
} }
// all iterators have full buffer return false
} }
func (w *lookupWalker) runLookup() { func (w *lookupWalker) runLookup(done chan struct{}) {
w.lookup(func(n *enode.Node) { w.lookup(func(n *enode.Node) {
select { for _, it := range w.liveIters() {
case w.foundNode <- n: it.deliver(n)
case <-w.closeCh:
} }
}) })
w.lookupDone <- struct{}{} close(done)
}
func (w *lookupWalker) setLiveIters(iters map[*lookupIterator]struct{}) {
s := make([]*lookupIterator, 0, len(iters))
for it := range iters {
s = append(s, it)
}
w.liveItersVal.Store(s)
}
func (w *lookupWalker) liveIters() []*lookupIterator {
return w.liveItersVal.Load().([]*lookupIterator)
} }
// lookupIterator is a sequence of discovered nodes. // lookupIterator is a sequence of discovered nodes.
type lookupIterator struct { type lookupIterator struct {
cur *enode.Node cur *enode.Node
w *lookupWalker walker *lookupWalker
mu sync.Mutex filter filterFunc
cond *sync.Cond mu sync.Mutex
buf []*enode.Node cond *sync.Cond
buf []*enode.Node
} }
const lookupIteratorBuffer = 100 const lookupIteratorBuffer = 100
func (w *lookupWalker) newIterator() *lookupIterator { type filterFunc func(*enode.Node) bool
it := &lookupIterator{w: w, buf: make([]*enode.Node, 0, lookupIteratorBuffer)}
func (w *lookupWalker) newIterator(filter filterFunc) *lookupIterator {
if filter == nil {
filter = func(*enode.Node) bool { return true }
}
it := &lookupIterator{
walker: w,
filter: filter,
buf: make([]*enode.Node, 0, lookupIteratorBuffer),
}
it.cond = sync.NewCond(&it.mu) it.cond = sync.NewCond(&it.mu)
// Register the iterator with walker.
select { select {
case w.newIterCh <- it: case w.newIterCh <- it:
case <-w.closeCh: case <-w.closeCh:
@ -179,8 +201,8 @@ func (w *lookupWalker) newIterator() *lookupIterator {
func (it *lookupIterator) Next() bool { func (it *lookupIterator) Next() bool {
select { select {
case it.w.triggerCh <- struct{}{}: case it.walker.triggerCh <- struct{}{}:
case <-it.w.closeCh: case <-it.walker.closeCh:
} }
it.cur = nil it.cur = nil
@ -205,8 +227,8 @@ func (it *lookupIterator) Node() *enode.Node {
func (it *lookupIterator) Close() { func (it *lookupIterator) Close() {
select { select {
case it.w.delIterCh <- it: case it.walker.delIterCh <- it:
case <-it.w.closeCh: case <-it.walker.closeCh:
} }
it.close() it.close()
} }
@ -221,11 +243,15 @@ func (it *lookupIterator) close() {
} }
} }
// deliver sends a node to the iterator buffer. // deliver places a node into the iterator buffer.
func (it *lookupIterator) deliver(n *enode.Node) { func (it *lookupIterator) deliver(n *enode.Node) {
it.mu.Lock() it.mu.Lock()
defer it.mu.Unlock() defer it.mu.Unlock()
if it.buf == nil || !it.filter(n) {
return
}
// Place in buffer, overwriting a random entry when at capacity.
if len(it.buf) == lookupIteratorBuffer { if len(it.buf) == lookupIteratorBuffer {
it.buf[rand.Intn(len(it.buf))] = n it.buf[rand.Intn(len(it.buf))] = n
} else { } else {
@ -234,6 +260,7 @@ func (it *lookupIterator) deliver(n *enode.Node) {
it.cond.Signal() it.cond.Signal()
} }
// needsNodes reports whether the iterator is low on nodes.
func (it *lookupIterator) needsNodes() bool { func (it *lookupIterator) needsNodes() bool {
it.mu.Lock() it.mu.Lock()
defer it.mu.Unlock() defer it.mu.Unlock()

View file

@ -64,7 +64,7 @@ func TestLookupIterator(t *testing.T) {
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
wg.Add(1) wg.Add(1)
go testIterator(test.newIterator()) go testIterator(test.newIterator(nil))
} }
test.serveOneLookup(testNodes[:10]) test.serveOneLookup(testNodes[:10])
@ -78,7 +78,7 @@ func TestLookupIterator(t *testing.T) {
func TestLookupIteratorClose(t *testing.T) { func TestLookupIteratorClose(t *testing.T) {
test := newLookupWalkerTest() test := newLookupWalkerTest()
defer test.close() defer test.close()
it := test.newIterator() it := test.newIterator(nil)
go func() { go func() {
time.Sleep(200 * time.Millisecond) time.Sleep(200 * time.Millisecond)
@ -96,7 +96,7 @@ func TestLookupIteratorDropStale(t *testing.T) {
lookupDone = make(chan struct{}) lookupDone = make(chan struct{})
) )
defer test.close() defer test.close()
it := test.newIterator() it := test.newIterator(nil)
go func() { go func() {
test.serveOneLookup(testNodes) test.serveOneLookup(testNodes)
close(lookupDone) close(lookupDone)
@ -126,7 +126,7 @@ func TestLookupIteratorDropStale(t *testing.T) {
func TestLookupIteratorDrained(t *testing.T) { func TestLookupIteratorDrained(t *testing.T) {
var ( var (
test = newLookupWalkerTest() test = newLookupWalkerTest()
it = test.newIterator() it = test.newIterator(nil)
testNodes = makeTestNodes(2 * lookupIteratorBuffer) testNodes = makeTestNodes(2 * lookupIteratorBuffer)
) )

View file

@ -305,8 +305,12 @@ 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() discutil.Iterator { //
return t.randomWalk.newIterator() // All iterators share the same random walk to minimize network traffic. Discovered nodes
// are checked against the filter function and returned by the iterator only when the
// filter returns true.
func (t *UDPv4) RandomNodes(filter func(*enode.Node) bool) discutil.Iterator {
return t.randomWalk.newIterator(filter)
} }
// LookupRandom finds random nodes in the network. // LookupRandom finds random nodes in the network.