github.com/arieschain/arieschain@v0.0.0-20191023063405-37c074544356/les/serverpool.go (about) 1 // Package les implements the Light Ethereum Subprotocol. 2 package les 3 4 import ( 5 "fmt" 6 "io" 7 "math" 8 "math/rand" 9 "net" 10 "strconv" 11 "sync" 12 "time" 13 14 "github.com/quickchainproject/quickchain/common/mclock" 15 "github.com/quickchainproject/quickchain/qctdb" 16 "github.com/quickchainproject/quickchain/log" 17 "github.com/quickchainproject/quickchain/p2p" 18 "github.com/quickchainproject/quickchain/p2p/discover" 19 "github.com/quickchainproject/quickchain/p2p/discv5" 20 "github.com/quickchainproject/quickchain/rlp" 21 ) 22 23 const ( 24 // After a connection has been ended or timed out, there is a waiting period 25 // before it can be selected for connection again. 26 // waiting period = base delay * (1 + random(1)) 27 // base delay = shortRetryDelay for the first shortRetryCnt times after a 28 // successful connection, after that longRetryDelay is applied 29 shortRetryCnt = 5 30 shortRetryDelay = time.Second * 5 31 longRetryDelay = time.Minute * 10 32 // maxNewEntries is the maximum number of newly discovered (never connected) nodes. 33 // If the limit is reached, the least recently discovered one is thrown out. 34 maxNewEntries = 1000 35 // maxKnownEntries is the maximum number of known (already connected) nodes. 36 // If the limit is reached, the least recently connected one is thrown out. 37 // (not that unlike new entries, known entries are persistent) 38 maxKnownEntries = 1000 39 // target for simultaneously connected servers 40 targetServerCount = 5 41 // target for servers selected from the known table 42 // (we leave room for trying new ones if there is any) 43 targetKnownSelect = 3 44 // after dialTimeout, consider the server unavailable and adjust statistics 45 dialTimeout = time.Second * 30 46 // targetConnTime is the minimum expected connection duration before a server 47 // drops a client without any specific reason 48 targetConnTime = time.Minute * 10 49 // new entry selection weight calculation based on most recent discovery time: 50 // unity until discoverExpireStart, then exponential decay with discoverExpireConst 51 discoverExpireStart = time.Minute * 20 52 discoverExpireConst = time.Minute * 20 53 // known entry selection weight is dropped by a factor of exp(-failDropLn) after 54 // each unsuccessful connection (restored after a successful one) 55 failDropLn = 0.1 56 // known node connection success and quality statistics have a long term average 57 // and a short term value which is adjusted exponentially with a factor of 58 // pstatRecentAdjust with each dial/connection and also returned exponentially 59 // to the average with the time constant pstatReturnToMeanTC 60 pstatReturnToMeanTC = time.Hour 61 // node address selection weight is dropped by a factor of exp(-addrFailDropLn) after 62 // each unsuccessful connection (restored after a successful one) 63 addrFailDropLn = math.Ln2 64 // responseScoreTC and delayScoreTC are exponential decay time constants for 65 // calculating selection chances from response times and block delay times 66 responseScoreTC = time.Millisecond * 100 67 delayScoreTC = time.Second * 5 68 timeoutPow = 10 69 // initStatsWeight is used to initialize previously unknown peers with good 70 // statistics to give a chance to prove themselves 71 initStatsWeight = 1 72 ) 73 74 // serverPool implements a pool for storing and selecting newly discovered and already 75 // known light server nodes. It received discovered nodes, stores statistics about 76 // known nodes and takes care of always having enough good quality servers connected. 77 type serverPool struct { 78 db qctdb.Database 79 dbKey []byte 80 server *p2p.Server 81 quit chan struct{} 82 wg *sync.WaitGroup 83 connWg sync.WaitGroup 84 85 topic discv5.Topic 86 87 discSetPeriod chan time.Duration 88 discNodes chan *discv5.Node 89 discLookups chan bool 90 91 entries map[discover.NodeID]*poolEntry 92 lock sync.Mutex 93 timeout, enableRetry chan *poolEntry 94 adjustStats chan poolStatAdjust 95 96 knownQueue, newQueue poolEntryQueue 97 knownSelect, newSelect *weightedRandomSelect 98 knownSelected, newSelected int 99 fastDiscover bool 100 } 101 102 // newServerPool creates a new serverPool instance 103 func newServerPool(db qctdb.Database, quit chan struct{}, wg *sync.WaitGroup) *serverPool { 104 pool := &serverPool{ 105 db: db, 106 quit: quit, 107 wg: wg, 108 entries: make(map[discover.NodeID]*poolEntry), 109 timeout: make(chan *poolEntry, 1), 110 adjustStats: make(chan poolStatAdjust, 100), 111 enableRetry: make(chan *poolEntry, 1), 112 knownSelect: newWeightedRandomSelect(), 113 newSelect: newWeightedRandomSelect(), 114 fastDiscover: true, 115 } 116 pool.knownQueue = newPoolEntryQueue(maxKnownEntries, pool.removeEntry) 117 pool.newQueue = newPoolEntryQueue(maxNewEntries, pool.removeEntry) 118 return pool 119 } 120 121 func (pool *serverPool) start(server *p2p.Server, topic discv5.Topic) { 122 pool.server = server 123 pool.topic = topic 124 pool.dbKey = append([]byte("serverPool/"), []byte(topic)...) 125 pool.wg.Add(1) 126 pool.loadNodes() 127 128 if pool.server.DiscV5 != nil { 129 pool.discSetPeriod = make(chan time.Duration, 1) 130 pool.discNodes = make(chan *discv5.Node, 100) 131 pool.discLookups = make(chan bool, 100) 132 go pool.server.DiscV5.SearchTopic(pool.topic, pool.discSetPeriod, pool.discNodes, pool.discLookups) 133 } 134 135 go pool.eventLoop() 136 pool.checkDial() 137 } 138 139 // connect should be called upon any incoming connection. If the connection has been 140 // dialed by the server pool recently, the appropriate pool entry is returned. 141 // Otherwise, the connection should be rejected. 142 // Note that whenever a connection has been accepted and a pool entry has been returned, 143 // disconnect should also always be called. 144 func (pool *serverPool) connect(p *peer, ip net.IP, port uint16) *poolEntry { 145 pool.lock.Lock() 146 defer pool.lock.Unlock() 147 entry := pool.entries[p.ID()] 148 if entry == nil { 149 entry = pool.findOrNewNode(p.ID(), ip, port) 150 } 151 p.Log().Debug("Connecting to new peer", "state", entry.state) 152 if entry.state == psConnected || entry.state == psRegistered { 153 return nil 154 } 155 pool.connWg.Add(1) 156 entry.peer = p 157 entry.state = psConnected 158 addr := &poolEntryAddress{ 159 ip: ip, 160 port: port, 161 lastSeen: mclock.Now(), 162 } 163 entry.lastConnected = addr 164 entry.addr = make(map[string]*poolEntryAddress) 165 entry.addr[addr.strKey()] = addr 166 entry.addrSelect = *newWeightedRandomSelect() 167 entry.addrSelect.update(addr) 168 return entry 169 } 170 171 // registered should be called after a successful handshake 172 func (pool *serverPool) registered(entry *poolEntry) { 173 log.Debug("Registered new entry", "enode", entry.id) 174 pool.lock.Lock() 175 defer pool.lock.Unlock() 176 177 entry.state = psRegistered 178 entry.regTime = mclock.Now() 179 if !entry.known { 180 pool.newQueue.remove(entry) 181 entry.known = true 182 } 183 pool.knownQueue.setLatest(entry) 184 entry.shortRetry = shortRetryCnt 185 } 186 187 // disconnect should be called when ending a connection. Service quality statistics 188 // can be updated optionally (not updated if no registration happened, in this case 189 // only connection statistics are updated, just like in case of timeout) 190 func (pool *serverPool) disconnect(entry *poolEntry) { 191 log.Debug("Disconnected old entry", "enode", entry.id) 192 pool.lock.Lock() 193 defer pool.lock.Unlock() 194 195 if entry.state == psRegistered { 196 connTime := mclock.Now() - entry.regTime 197 connAdjust := float64(connTime) / float64(targetConnTime) 198 if connAdjust > 1 { 199 connAdjust = 1 200 } 201 stopped := false 202 select { 203 case <-pool.quit: 204 stopped = true 205 default: 206 } 207 if stopped { 208 entry.connectStats.add(1, connAdjust) 209 } else { 210 entry.connectStats.add(connAdjust, 1) 211 } 212 } 213 214 entry.state = psNotConnected 215 if entry.knownSelected { 216 pool.knownSelected-- 217 } else { 218 pool.newSelected-- 219 } 220 pool.setRetryDial(entry) 221 pool.connWg.Done() 222 } 223 224 const ( 225 pseBlockDelay = iota 226 pseResponseTime 227 pseResponseTimeout 228 ) 229 230 // poolStatAdjust records are sent to adjust peer block delay/response time statistics 231 type poolStatAdjust struct { 232 adjustType int 233 entry *poolEntry 234 time time.Duration 235 } 236 237 // adjustBlockDelay adjusts the block announce delay statistics of a node 238 func (pool *serverPool) adjustBlockDelay(entry *poolEntry, time time.Duration) { 239 if entry == nil { 240 return 241 } 242 pool.adjustStats <- poolStatAdjust{pseBlockDelay, entry, time} 243 } 244 245 // adjustResponseTime adjusts the request response time statistics of a node 246 func (pool *serverPool) adjustResponseTime(entry *poolEntry, time time.Duration, timeout bool) { 247 if entry == nil { 248 return 249 } 250 if timeout { 251 pool.adjustStats <- poolStatAdjust{pseResponseTimeout, entry, time} 252 } else { 253 pool.adjustStats <- poolStatAdjust{pseResponseTime, entry, time} 254 } 255 } 256 257 // eventLoop handles pool events and mutex locking for all internal functions 258 func (pool *serverPool) eventLoop() { 259 lookupCnt := 0 260 var convTime mclock.AbsTime 261 if pool.discSetPeriod != nil { 262 pool.discSetPeriod <- time.Millisecond * 100 263 } 264 for { 265 select { 266 case entry := <-pool.timeout: 267 pool.lock.Lock() 268 if !entry.removed { 269 pool.checkDialTimeout(entry) 270 } 271 pool.lock.Unlock() 272 273 case entry := <-pool.enableRetry: 274 pool.lock.Lock() 275 if !entry.removed { 276 entry.delayedRetry = false 277 pool.updateCheckDial(entry) 278 } 279 pool.lock.Unlock() 280 281 case adj := <-pool.adjustStats: 282 pool.lock.Lock() 283 switch adj.adjustType { 284 case pseBlockDelay: 285 adj.entry.delayStats.add(float64(adj.time), 1) 286 case pseResponseTime: 287 adj.entry.responseStats.add(float64(adj.time), 1) 288 adj.entry.timeoutStats.add(0, 1) 289 case pseResponseTimeout: 290 adj.entry.timeoutStats.add(1, 1) 291 } 292 pool.lock.Unlock() 293 294 case node := <-pool.discNodes: 295 pool.lock.Lock() 296 entry := pool.findOrNewNode(discover.NodeID(node.ID), node.IP, node.TCP) 297 pool.updateCheckDial(entry) 298 pool.lock.Unlock() 299 300 case conv := <-pool.discLookups: 301 if conv { 302 if lookupCnt == 0 { 303 convTime = mclock.Now() 304 } 305 lookupCnt++ 306 if pool.fastDiscover && (lookupCnt == 50 || time.Duration(mclock.Now()-convTime) > time.Minute) { 307 pool.fastDiscover = false 308 if pool.discSetPeriod != nil { 309 pool.discSetPeriod <- time.Minute 310 } 311 } 312 } 313 314 case <-pool.quit: 315 if pool.discSetPeriod != nil { 316 close(pool.discSetPeriod) 317 } 318 pool.connWg.Wait() 319 pool.saveNodes() 320 pool.wg.Done() 321 return 322 323 } 324 } 325 } 326 327 func (pool *serverPool) findOrNewNode(id discover.NodeID, ip net.IP, port uint16) *poolEntry { 328 now := mclock.Now() 329 entry := pool.entries[id] 330 if entry == nil { 331 log.Debug("Discovered new entry", "id", id) 332 entry = &poolEntry{ 333 id: id, 334 addr: make(map[string]*poolEntryAddress), 335 addrSelect: *newWeightedRandomSelect(), 336 shortRetry: shortRetryCnt, 337 } 338 pool.entries[id] = entry 339 // initialize previously unknown peers with good statistics to give a chance to prove themselves 340 entry.connectStats.add(1, initStatsWeight) 341 entry.delayStats.add(0, initStatsWeight) 342 entry.responseStats.add(0, initStatsWeight) 343 entry.timeoutStats.add(0, initStatsWeight) 344 } 345 entry.lastDiscovered = now 346 addr := &poolEntryAddress{ 347 ip: ip, 348 port: port, 349 } 350 if a, ok := entry.addr[addr.strKey()]; ok { 351 addr = a 352 } else { 353 entry.addr[addr.strKey()] = addr 354 } 355 addr.lastSeen = now 356 entry.addrSelect.update(addr) 357 if !entry.known { 358 pool.newQueue.setLatest(entry) 359 } 360 return entry 361 } 362 363 // loadNodes loads known nodes and their statistics from the database 364 func (pool *serverPool) loadNodes() { 365 enc, err := pool.db.Get(pool.dbKey) 366 if err != nil { 367 return 368 } 369 var list []*poolEntry 370 err = rlp.DecodeBytes(enc, &list) 371 if err != nil { 372 log.Debug("Failed to decode node list", "err", err) 373 return 374 } 375 for _, e := range list { 376 log.Debug("Loaded server stats", "id", e.id, "fails", e.lastConnected.fails, 377 "conn", fmt.Sprintf("%v/%v", e.connectStats.avg, e.connectStats.weight), 378 "delay", fmt.Sprintf("%v/%v", time.Duration(e.delayStats.avg), e.delayStats.weight), 379 "response", fmt.Sprintf("%v/%v", time.Duration(e.responseStats.avg), e.responseStats.weight), 380 "timeout", fmt.Sprintf("%v/%v", e.timeoutStats.avg, e.timeoutStats.weight)) 381 pool.entries[e.id] = e 382 pool.knownQueue.setLatest(e) 383 pool.knownSelect.update((*knownEntry)(e)) 384 } 385 } 386 387 // saveNodes saves known nodes and their statistics into the database. Nodes are 388 // ordered from least to most recently connected. 389 func (pool *serverPool) saveNodes() { 390 list := make([]*poolEntry, len(pool.knownQueue.queue)) 391 for i := range list { 392 list[i] = pool.knownQueue.fetchOldest() 393 } 394 enc, err := rlp.EncodeToBytes(list) 395 if err == nil { 396 pool.db.Put(pool.dbKey, enc) 397 } 398 } 399 400 // removeEntry removes a pool entry when the entry count limit is reached. 401 // Note that it is called by the new/known queues from which the entry has already 402 // been removed so removing it from the queues is not necessary. 403 func (pool *serverPool) removeEntry(entry *poolEntry) { 404 pool.newSelect.remove((*discoveredEntry)(entry)) 405 pool.knownSelect.remove((*knownEntry)(entry)) 406 entry.removed = true 407 delete(pool.entries, entry.id) 408 } 409 410 // setRetryDial starts the timer which will enable dialing a certain node again 411 func (pool *serverPool) setRetryDial(entry *poolEntry) { 412 delay := longRetryDelay 413 if entry.shortRetry > 0 { 414 entry.shortRetry-- 415 delay = shortRetryDelay 416 } 417 delay += time.Duration(rand.Int63n(int64(delay) + 1)) 418 entry.delayedRetry = true 419 go func() { 420 select { 421 case <-pool.quit: 422 case <-time.After(delay): 423 select { 424 case <-pool.quit: 425 case pool.enableRetry <- entry: 426 } 427 } 428 }() 429 } 430 431 // updateCheckDial is called when an entry can potentially be dialed again. It updates 432 // its selection weights and checks if new dials can/should be made. 433 func (pool *serverPool) updateCheckDial(entry *poolEntry) { 434 pool.newSelect.update((*discoveredEntry)(entry)) 435 pool.knownSelect.update((*knownEntry)(entry)) 436 pool.checkDial() 437 } 438 439 // checkDial checks if new dials can/should be made. It tries to select servers both 440 // based on good statistics and recent discovery. 441 func (pool *serverPool) checkDial() { 442 fillWithKnownSelects := !pool.fastDiscover 443 for pool.knownSelected < targetKnownSelect { 444 entry := pool.knownSelect.choose() 445 if entry == nil { 446 fillWithKnownSelects = false 447 break 448 } 449 pool.dial((*poolEntry)(entry.(*knownEntry)), true) 450 } 451 for pool.knownSelected+pool.newSelected < targetServerCount { 452 entry := pool.newSelect.choose() 453 if entry == nil { 454 break 455 } 456 pool.dial((*poolEntry)(entry.(*discoveredEntry)), false) 457 } 458 if fillWithKnownSelects { 459 // no more newly discovered nodes to select and since fast discover period 460 // is over, we probably won't find more in the near future so select more 461 // known entries if possible 462 for pool.knownSelected < targetServerCount { 463 entry := pool.knownSelect.choose() 464 if entry == nil { 465 break 466 } 467 pool.dial((*poolEntry)(entry.(*knownEntry)), true) 468 } 469 } 470 } 471 472 // dial initiates a new connection 473 func (pool *serverPool) dial(entry *poolEntry, knownSelected bool) { 474 if pool.server == nil || entry.state != psNotConnected { 475 return 476 } 477 entry.state = psDialed 478 entry.knownSelected = knownSelected 479 if knownSelected { 480 pool.knownSelected++ 481 } else { 482 pool.newSelected++ 483 } 484 addr := entry.addrSelect.choose().(*poolEntryAddress) 485 log.Debug("Dialing new peer", "lesaddr", entry.id.String()+"@"+addr.strKey(), "set", len(entry.addr), "known", knownSelected) 486 entry.dialed = addr 487 go func() { 488 pool.server.AddPeer(discover.NewNode(entry.id, addr.ip, addr.port, addr.port)) 489 select { 490 case <-pool.quit: 491 case <-time.After(dialTimeout): 492 select { 493 case <-pool.quit: 494 case pool.timeout <- entry: 495 } 496 } 497 }() 498 } 499 500 // checkDialTimeout checks if the node is still in dialed state and if so, resets it 501 // and adjusts connection statistics accordingly. 502 func (pool *serverPool) checkDialTimeout(entry *poolEntry) { 503 if entry.state != psDialed { 504 return 505 } 506 log.Debug("Dial timeout", "lesaddr", entry.id.String()+"@"+entry.dialed.strKey()) 507 entry.state = psNotConnected 508 if entry.knownSelected { 509 pool.knownSelected-- 510 } else { 511 pool.newSelected-- 512 } 513 entry.connectStats.add(0, 1) 514 entry.dialed.fails++ 515 pool.setRetryDial(entry) 516 } 517 518 const ( 519 psNotConnected = iota 520 psDialed 521 psConnected 522 psRegistered 523 ) 524 525 // poolEntry represents a server node and stores its current state and statistics. 526 type poolEntry struct { 527 peer *peer 528 id discover.NodeID 529 addr map[string]*poolEntryAddress 530 lastConnected, dialed *poolEntryAddress 531 addrSelect weightedRandomSelect 532 533 lastDiscovered mclock.AbsTime 534 known, knownSelected bool 535 connectStats, delayStats poolStats 536 responseStats, timeoutStats poolStats 537 state int 538 regTime mclock.AbsTime 539 queueIdx int 540 removed bool 541 542 delayedRetry bool 543 shortRetry int 544 } 545 546 func (e *poolEntry) EncodeRLP(w io.Writer) error { 547 return rlp.Encode(w, []interface{}{e.id, e.lastConnected.ip, e.lastConnected.port, e.lastConnected.fails, &e.connectStats, &e.delayStats, &e.responseStats, &e.timeoutStats}) 548 } 549 550 func (e *poolEntry) DecodeRLP(s *rlp.Stream) error { 551 var entry struct { 552 ID discover.NodeID 553 IP net.IP 554 Port uint16 555 Fails uint 556 CStat, DStat, RStat, TStat poolStats 557 } 558 if err := s.Decode(&entry); err != nil { 559 return err 560 } 561 addr := &poolEntryAddress{ip: entry.IP, port: entry.Port, fails: entry.Fails, lastSeen: mclock.Now()} 562 e.id = entry.ID 563 e.addr = make(map[string]*poolEntryAddress) 564 e.addr[addr.strKey()] = addr 565 e.addrSelect = *newWeightedRandomSelect() 566 e.addrSelect.update(addr) 567 e.lastConnected = addr 568 e.connectStats = entry.CStat 569 e.delayStats = entry.DStat 570 e.responseStats = entry.RStat 571 e.timeoutStats = entry.TStat 572 e.shortRetry = shortRetryCnt 573 e.known = true 574 return nil 575 } 576 577 // discoveredEntry implements wrsItem 578 type discoveredEntry poolEntry 579 580 // Weight calculates random selection weight for newly discovered entries 581 func (e *discoveredEntry) Weight() int64 { 582 if e.state != psNotConnected || e.delayedRetry { 583 return 0 584 } 585 t := time.Duration(mclock.Now() - e.lastDiscovered) 586 if t <= discoverExpireStart { 587 return 1000000000 588 } else { 589 return int64(1000000000 * math.Exp(-float64(t-discoverExpireStart)/float64(discoverExpireConst))) 590 } 591 } 592 593 // knownEntry implements wrsItem 594 type knownEntry poolEntry 595 596 // Weight calculates random selection weight for known entries 597 func (e *knownEntry) Weight() int64 { 598 if e.state != psNotConnected || !e.known || e.delayedRetry { 599 return 0 600 } 601 return int64(1000000000 * e.connectStats.recentAvg() * math.Exp(-float64(e.lastConnected.fails)*failDropLn-e.responseStats.recentAvg()/float64(responseScoreTC)-e.delayStats.recentAvg()/float64(delayScoreTC)) * math.Pow(1-e.timeoutStats.recentAvg(), timeoutPow)) 602 } 603 604 // poolEntryAddress is a separate object because currently it is necessary to remember 605 // multiple potential network addresses for a pool entry. This will be removed after 606 // the final implementation of v5 discovery which will retrieve signed and serial 607 // numbered advertisements, making it clear which IP/port is the latest one. 608 type poolEntryAddress struct { 609 ip net.IP 610 port uint16 611 lastSeen mclock.AbsTime // last time it was discovered, connected or loaded from db 612 fails uint // connection failures since last successful connection (persistent) 613 } 614 615 func (a *poolEntryAddress) Weight() int64 { 616 t := time.Duration(mclock.Now() - a.lastSeen) 617 return int64(1000000*math.Exp(-float64(t)/float64(discoverExpireConst)-float64(a.fails)*addrFailDropLn)) + 1 618 } 619 620 func (a *poolEntryAddress) strKey() string { 621 return a.ip.String() + ":" + strconv.Itoa(int(a.port)) 622 } 623 624 // poolStats implement statistics for a certain quantity with a long term average 625 // and a short term value which is adjusted exponentially with a factor of 626 // pstatRecentAdjust with each update and also returned exponentially to the 627 // average with the time constant pstatReturnToMeanTC 628 type poolStats struct { 629 sum, weight, avg, recent float64 630 lastRecalc mclock.AbsTime 631 } 632 633 // init initializes stats with a long term sum/update count pair retrieved from the database 634 func (s *poolStats) init(sum, weight float64) { 635 s.sum = sum 636 s.weight = weight 637 var avg float64 638 if weight > 0 { 639 avg = s.sum / weight 640 } 641 s.avg = avg 642 s.recent = avg 643 s.lastRecalc = mclock.Now() 644 } 645 646 // recalc recalculates recent value return-to-mean and long term average 647 func (s *poolStats) recalc() { 648 now := mclock.Now() 649 s.recent = s.avg + (s.recent-s.avg)*math.Exp(-float64(now-s.lastRecalc)/float64(pstatReturnToMeanTC)) 650 if s.sum == 0 { 651 s.avg = 0 652 } else { 653 if s.sum > s.weight*1e30 { 654 s.avg = 1e30 655 } else { 656 s.avg = s.sum / s.weight 657 } 658 } 659 s.lastRecalc = now 660 } 661 662 // add updates the stats with a new value 663 func (s *poolStats) add(value, weight float64) { 664 s.weight += weight 665 s.sum += value * weight 666 s.recalc() 667 } 668 669 // recentAvg returns the short-term adjusted average 670 func (s *poolStats) recentAvg() float64 { 671 s.recalc() 672 return s.recent 673 } 674 675 func (s *poolStats) EncodeRLP(w io.Writer) error { 676 return rlp.Encode(w, []interface{}{math.Float64bits(s.sum), math.Float64bits(s.weight)}) 677 } 678 679 func (s *poolStats) DecodeRLP(st *rlp.Stream) error { 680 var stats struct { 681 SumUint, WeightUint uint64 682 } 683 if err := st.Decode(&stats); err != nil { 684 return err 685 } 686 s.init(math.Float64frombits(stats.SumUint), math.Float64frombits(stats.WeightUint)) 687 return nil 688 } 689 690 // poolEntryQueue keeps track of its least recently accessed entries and removes 691 // them when the number of entries reaches the limit 692 type poolEntryQueue struct { 693 queue map[int]*poolEntry // known nodes indexed by their latest lastConnCnt value 694 newPtr, oldPtr, maxCnt int 695 removeFromPool func(*poolEntry) 696 } 697 698 // newPoolEntryQueue returns a new poolEntryQueue 699 func newPoolEntryQueue(maxCnt int, removeFromPool func(*poolEntry)) poolEntryQueue { 700 return poolEntryQueue{queue: make(map[int]*poolEntry), maxCnt: maxCnt, removeFromPool: removeFromPool} 701 } 702 703 // fetchOldest returns and removes the least recently accessed entry 704 func (q *poolEntryQueue) fetchOldest() *poolEntry { 705 if len(q.queue) == 0 { 706 return nil 707 } 708 for { 709 if e := q.queue[q.oldPtr]; e != nil { 710 delete(q.queue, q.oldPtr) 711 q.oldPtr++ 712 return e 713 } 714 q.oldPtr++ 715 } 716 } 717 718 // remove removes an entry from the queue 719 func (q *poolEntryQueue) remove(entry *poolEntry) { 720 if q.queue[entry.queueIdx] == entry { 721 delete(q.queue, entry.queueIdx) 722 } 723 } 724 725 // setLatest adds or updates a recently accessed entry. It also checks if an old entry 726 // needs to be removed and removes it from the parent pool too with a callback function. 727 func (q *poolEntryQueue) setLatest(entry *poolEntry) { 728 if q.queue[entry.queueIdx] == entry { 729 delete(q.queue, entry.queueIdx) 730 } else { 731 if len(q.queue) == q.maxCnt { 732 e := q.fetchOldest() 733 q.remove(e) 734 q.removeFromPool(e) 735 } 736 } 737 entry.queueIdx = q.newPtr 738 q.queue[entry.queueIdx] = entry 739 q.newPtr++ 740 }