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