github.com/ethereum/go-ethereum@v1.16.1/p2p/enode/iter.go (about)

     1  // Copyright 2019 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package enode
    18  
    19  import (
    20  	"context"
    21  	"sync"
    22  	"time"
    23  )
    24  
    25  // Iterator represents a sequence of nodes. The Next method moves to the next node in the
    26  // sequence. It returns false when the sequence has ended or the iterator is closed. Close
    27  // may be called concurrently with Next and Node, and interrupts Next if it is blocked.
    28  type Iterator interface {
    29  	Next() bool  // moves to next node
    30  	Node() *Node // returns current node
    31  	Close()      // ends the iterator
    32  }
    33  
    34  // SourceIterator represents a sequence of nodes like [Iterator]
    35  // Each node also has a named 'source'.
    36  type SourceIterator interface {
    37  	Iterator
    38  	NodeSource() string // source of current node
    39  }
    40  
    41  // WithSource attaches a 'source name' to an iterator.
    42  func WithSourceName(name string, it Iterator) SourceIterator {
    43  	return sourceIter{it, name}
    44  }
    45  
    46  func ensureSourceIter(it Iterator) SourceIterator {
    47  	if si, ok := it.(SourceIterator); ok {
    48  		return si
    49  	}
    50  	return WithSourceName("", it)
    51  }
    52  
    53  type sourceIter struct {
    54  	Iterator
    55  	name string
    56  }
    57  
    58  // NodeSource implements IteratorSource.
    59  func (it sourceIter) NodeSource() string {
    60  	return it.name
    61  }
    62  
    63  type iteratorItem struct {
    64  	n      *Node
    65  	source string
    66  }
    67  
    68  // ReadNodes reads at most n nodes from the given iterator. The return value contains no
    69  // duplicates and no nil values. To prevent looping indefinitely for small repeating node
    70  // sequences, this function calls Next at most n times.
    71  func ReadNodes(it Iterator, n int) []*Node {
    72  	seen := make(map[ID]*Node, n)
    73  	for i := 0; i < n && it.Next(); i++ {
    74  		// Remove duplicates, keeping the node with higher seq.
    75  		node := it.Node()
    76  		prevNode, ok := seen[node.ID()]
    77  		if ok && prevNode.Seq() > node.Seq() {
    78  			continue
    79  		}
    80  		seen[node.ID()] = node
    81  	}
    82  	result := make([]*Node, 0, len(seen))
    83  	for _, node := range seen {
    84  		result = append(result, node)
    85  	}
    86  	return result
    87  }
    88  
    89  // IterNodes makes an iterator which runs through the given nodes once.
    90  func IterNodes(nodes []*Node) Iterator {
    91  	return &sliceIter{nodes: nodes, index: -1}
    92  }
    93  
    94  // CycleNodes makes an iterator which cycles through the given nodes indefinitely.
    95  func CycleNodes(nodes []*Node) Iterator {
    96  	return &sliceIter{nodes: nodes, index: -1, cycle: true}
    97  }
    98  
    99  type sliceIter struct {
   100  	mu    sync.Mutex
   101  	nodes []*Node
   102  	index int
   103  	cycle bool
   104  }
   105  
   106  func (it *sliceIter) Next() bool {
   107  	it.mu.Lock()
   108  	defer it.mu.Unlock()
   109  
   110  	if len(it.nodes) == 0 {
   111  		return false
   112  	}
   113  	it.index++
   114  	if it.index == len(it.nodes) {
   115  		if it.cycle {
   116  			it.index = 0
   117  		} else {
   118  			it.nodes = nil
   119  			return false
   120  		}
   121  	}
   122  	return true
   123  }
   124  
   125  func (it *sliceIter) Node() *Node {
   126  	it.mu.Lock()
   127  	defer it.mu.Unlock()
   128  	if len(it.nodes) == 0 {
   129  		return nil
   130  	}
   131  	return it.nodes[it.index]
   132  }
   133  
   134  func (it *sliceIter) Close() {
   135  	it.mu.Lock()
   136  	defer it.mu.Unlock()
   137  
   138  	it.nodes = nil
   139  }
   140  
   141  // Filter wraps an iterator such that Next only returns nodes for which
   142  // the 'check' function returns true.
   143  func Filter(it Iterator, check func(*Node) bool) Iterator {
   144  	return &filterIter{ensureSourceIter(it), check}
   145  }
   146  
   147  type filterIter struct {
   148  	SourceIterator
   149  	check func(*Node) bool
   150  }
   151  
   152  func (f *filterIter) Next() bool {
   153  	for f.SourceIterator.Next() {
   154  		if f.check(f.Node()) {
   155  			return true
   156  		}
   157  	}
   158  	return false
   159  }
   160  
   161  // asyncFilterIter wraps an iterator such that Next only returns nodes for which
   162  // the 'check' function returns a (possibly modified) node.
   163  type asyncFilterIter struct {
   164  	it        SourceIterator    // the iterator to filter
   165  	slots     chan struct{}     // the slots for parallel checking
   166  	passed    chan iteratorItem // channel to collect passed nodes
   167  	cur       iteratorItem      // buffer to serve the Node call
   168  	cancel    context.CancelFunc
   169  	closeOnce sync.Once
   170  }
   171  
   172  type AsyncFilterFunc func(context.Context, *Node) *Node
   173  
   174  // AsyncFilter creates an iterator which checks nodes in parallel.
   175  // The 'check' function is called on multiple goroutines to filter each node
   176  // from the upstream iterator. When check returns nil, the node will be skipped.
   177  // It can also return a new node to be returned by the iterator instead of the .
   178  func AsyncFilter(it Iterator, check AsyncFilterFunc, workers int) Iterator {
   179  	f := &asyncFilterIter{
   180  		it:     ensureSourceIter(it),
   181  		slots:  make(chan struct{}, workers+1),
   182  		passed: make(chan iteratorItem),
   183  	}
   184  	for range cap(f.slots) {
   185  		f.slots <- struct{}{}
   186  	}
   187  	ctx, cancel := context.WithCancel(context.Background())
   188  	f.cancel = cancel
   189  
   190  	go func() {
   191  		select {
   192  		case <-ctx.Done():
   193  			return
   194  		case <-f.slots:
   195  		}
   196  		// read from the iterator and start checking nodes in parallel
   197  		// when a node is checked, it will be sent to the passed channel
   198  		// and the slot will be released
   199  		for f.it.Next() {
   200  			node := f.it.Node()
   201  			nodeSource := f.it.NodeSource()
   202  
   203  			// check the node async, in a separate goroutine
   204  			<-f.slots
   205  			go func() {
   206  				if nn := check(ctx, node); nn != nil {
   207  					item := iteratorItem{nn, nodeSource}
   208  					select {
   209  					case f.passed <- item:
   210  					case <-ctx.Done(): // bale out if downstream is already closed and not calling Next
   211  					}
   212  				}
   213  				f.slots <- struct{}{}
   214  			}()
   215  		}
   216  		// the iterator has ended
   217  		f.slots <- struct{}{}
   218  	}()
   219  
   220  	return f
   221  }
   222  
   223  // Next blocks until a node is available or the iterator is closed.
   224  func (f *asyncFilterIter) Next() bool {
   225  	var ok bool
   226  	f.cur, ok = <-f.passed
   227  	return ok
   228  }
   229  
   230  // Node returns the current node.
   231  func (f *asyncFilterIter) Node() *Node {
   232  	return f.cur.n
   233  }
   234  
   235  // NodeSource implements IteratorSource.
   236  func (f *asyncFilterIter) NodeSource() string {
   237  	return f.cur.source
   238  }
   239  
   240  // Close ends the iterator, also closing the wrapped iterator.
   241  func (f *asyncFilterIter) Close() {
   242  	f.closeOnce.Do(func() {
   243  		f.it.Close()
   244  		f.cancel()
   245  		for range cap(f.slots) {
   246  			<-f.slots
   247  		}
   248  		close(f.slots)
   249  		close(f.passed)
   250  	})
   251  }
   252  
   253  // bufferIter wraps an iterator and buffers the nodes it returns.
   254  // The buffer is pre-filled with the given size from the wrapped iterator.
   255  type bufferIter struct {
   256  	it        SourceIterator
   257  	buffer    chan iteratorItem
   258  	head      iteratorItem
   259  	closeOnce sync.Once
   260  }
   261  
   262  // NewBufferIter creates a new pre-fetch buffer of a given size.
   263  func NewBufferIter(it Iterator, size int) Iterator {
   264  	b := bufferIter{
   265  		it:     ensureSourceIter(it),
   266  		buffer: make(chan iteratorItem, size),
   267  	}
   268  
   269  	go func() {
   270  		// if the wrapped iterator ends, the buffer content will still be served.
   271  		defer close(b.buffer)
   272  		// If instead the bufferIterator is closed, we bail out of the loop.
   273  		for b.it.Next() {
   274  			item := iteratorItem{b.it.Node(), b.it.NodeSource()}
   275  			b.buffer <- item
   276  		}
   277  	}()
   278  	return &b
   279  }
   280  
   281  func (b *bufferIter) Next() bool {
   282  	var ok bool
   283  	b.head, ok = <-b.buffer
   284  	return ok
   285  }
   286  
   287  func (b *bufferIter) Node() *Node {
   288  	return b.head.n
   289  }
   290  
   291  func (b *bufferIter) NodeSource() string {
   292  	return b.head.source
   293  }
   294  
   295  func (b *bufferIter) Close() {
   296  	b.closeOnce.Do(func() {
   297  		b.it.Close()
   298  		// Drain buffer and wait for the goroutine to end.
   299  		for range b.buffer {
   300  		}
   301  	})
   302  }
   303  
   304  // FairMix aggregates multiple node iterators. The mixer itself is an iterator which ends
   305  // only when Close is called. Source iterators added via AddSource are removed from the
   306  // mix when they end.
   307  //
   308  // The distribution of nodes returned by Next is approximately fair, i.e. FairMix
   309  // attempts to draw from all sources equally often. However, if a certain source is slow
   310  // and doesn't return a node within the configured timeout, a node from any other source
   311  // will be returned.
   312  //
   313  // It's safe to call AddSource and Close concurrently with Next.
   314  type FairMix struct {
   315  	wg      sync.WaitGroup
   316  	fromAny chan iteratorItem
   317  	timeout time.Duration
   318  	cur     iteratorItem
   319  
   320  	mu      sync.Mutex
   321  	closed  chan struct{}
   322  	sources []*mixSource
   323  	last    int
   324  }
   325  
   326  type mixSource struct {
   327  	it      SourceIterator
   328  	next    chan iteratorItem
   329  	timeout time.Duration
   330  }
   331  
   332  // NewFairMix creates a mixer.
   333  //
   334  // The timeout specifies how long the mixer will wait for the next fairly-chosen source
   335  // before giving up and taking a node from any other source. A good way to set the timeout
   336  // is deciding how long you'd want to wait for a node on average. Passing a negative
   337  // timeout makes the mixer completely fair.
   338  func NewFairMix(timeout time.Duration) *FairMix {
   339  	m := &FairMix{
   340  		fromAny: make(chan iteratorItem),
   341  		closed:  make(chan struct{}),
   342  		timeout: timeout,
   343  	}
   344  	return m
   345  }
   346  
   347  // AddSource adds a source of nodes.
   348  func (m *FairMix) AddSource(it Iterator) {
   349  	m.mu.Lock()
   350  	defer m.mu.Unlock()
   351  
   352  	if m.closed == nil {
   353  		return
   354  	}
   355  	m.wg.Add(1)
   356  	source := &mixSource{
   357  		it:      ensureSourceIter(it),
   358  		next:    make(chan iteratorItem),
   359  		timeout: m.timeout,
   360  	}
   361  	m.sources = append(m.sources, source)
   362  	go m.runSource(m.closed, source)
   363  }
   364  
   365  // Close shuts down the mixer and all current sources.
   366  // Calling this is required to release resources associated with the mixer.
   367  func (m *FairMix) Close() {
   368  	m.mu.Lock()
   369  	defer m.mu.Unlock()
   370  
   371  	if m.closed == nil {
   372  		return
   373  	}
   374  	for _, s := range m.sources {
   375  		s.it.Close()
   376  	}
   377  	close(m.closed)
   378  	m.wg.Wait()
   379  	close(m.fromAny)
   380  	m.sources = nil
   381  	m.closed = nil
   382  }
   383  
   384  // Next returns a node from a random source.
   385  func (m *FairMix) Next() bool {
   386  	m.cur = iteratorItem{}
   387  
   388  	for {
   389  		source := m.pickSource()
   390  		if source == nil {
   391  			return m.nextFromAny()
   392  		}
   393  
   394  		var timeout <-chan time.Time
   395  		if source.timeout >= 0 {
   396  			timer := time.NewTimer(source.timeout)
   397  			timeout = timer.C
   398  			defer timer.Stop()
   399  		}
   400  
   401  		select {
   402  		case item, ok := <-source.next:
   403  			if ok {
   404  				// Here, the timeout is reset to the configured value
   405  				// because the source delivered a node.
   406  				source.timeout = m.timeout
   407  				m.cur = item
   408  				return true
   409  			}
   410  			// This source has ended.
   411  			m.deleteSource(source)
   412  		case <-timeout:
   413  			// The selected source did not deliver a node within the timeout, so the
   414  			// timeout duration is halved for next time. This is supposed to improve
   415  			// latency with stuck sources.
   416  			source.timeout /= 2
   417  			return m.nextFromAny()
   418  		}
   419  	}
   420  }
   421  
   422  // Node returns the current node.
   423  func (m *FairMix) Node() *Node {
   424  	return m.cur.n
   425  }
   426  
   427  // NodeSource returns the current node's source name.
   428  func (m *FairMix) NodeSource() string {
   429  	return m.cur.source
   430  }
   431  
   432  // nextFromAny is used when there are no sources or when the 'fair' choice
   433  // doesn't turn up a node quickly enough.
   434  func (m *FairMix) nextFromAny() bool {
   435  	item, ok := <-m.fromAny
   436  	if ok {
   437  		m.cur = item
   438  	}
   439  	return ok
   440  }
   441  
   442  // pickSource chooses the next source to read from, cycling through them in order.
   443  func (m *FairMix) pickSource() *mixSource {
   444  	m.mu.Lock()
   445  	defer m.mu.Unlock()
   446  
   447  	if len(m.sources) == 0 {
   448  		return nil
   449  	}
   450  	m.last = (m.last + 1) % len(m.sources)
   451  	return m.sources[m.last]
   452  }
   453  
   454  // deleteSource deletes a source.
   455  func (m *FairMix) deleteSource(s *mixSource) {
   456  	m.mu.Lock()
   457  	defer m.mu.Unlock()
   458  
   459  	for i := range m.sources {
   460  		if m.sources[i] == s {
   461  			copy(m.sources[i:], m.sources[i+1:])
   462  			m.sources[len(m.sources)-1] = nil
   463  			m.sources = m.sources[:len(m.sources)-1]
   464  			break
   465  		}
   466  	}
   467  }
   468  
   469  // runSource reads a single source in a loop.
   470  func (m *FairMix) runSource(closed chan struct{}, s *mixSource) {
   471  	defer m.wg.Done()
   472  	defer close(s.next)
   473  	for s.it.Next() {
   474  		item := iteratorItem{s.it.Node(), s.it.NodeSource()}
   475  		select {
   476  		case s.next <- item:
   477  		case m.fromAny <- item:
   478  		case <-closed:
   479  			return
   480  		}
   481  	}
   482  }