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 }