github.com/zhiqiangxu/go-ethereum@v1.9.16-0.20210824055606-be91cfdebc48/les/balance.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 les 18 19 import ( 20 "sync" 21 "time" 22 23 "github.com/zhiqiangxu/go-ethereum/common/mclock" 24 ) 25 26 const ( 27 balanceCallbackQueue = iota 28 balanceCallbackZero 29 balanceCallbackCount 30 ) 31 32 // balanceTracker keeps track of the positive and negative balances of a connected 33 // client and calculates actual and projected future priority values required by 34 // prque.LazyQueue. 35 type balanceTracker struct { 36 lock sync.Mutex 37 clock mclock.Clock 38 stopped bool 39 capacity uint64 40 balance balance 41 timeFactor, requestFactor float64 42 negTimeFactor, negRequestFactor float64 43 sumReqCost uint64 44 lastUpdate, nextUpdate, initTime mclock.AbsTime 45 updateEvent mclock.Timer 46 // since only a limited and fixed number of callbacks are needed, they are 47 // stored in a fixed size array ordered by priority threshold. 48 callbacks [balanceCallbackCount]balanceCallback 49 // callbackIndex maps balanceCallback constants to callbacks array indexes (-1 if not active) 50 callbackIndex [balanceCallbackCount]int 51 callbackCount int // number of active callbacks 52 } 53 54 // balance represents a pair of positive and negative balances 55 type balance struct { 56 pos, neg uint64 57 } 58 59 // balanceCallback represents a single callback that is activated when client priority 60 // reaches the given threshold 61 type balanceCallback struct { 62 id int 63 threshold int64 64 callback func() 65 } 66 67 // init initializes balanceTracker 68 func (bt *balanceTracker) init(clock mclock.Clock, capacity uint64) { 69 bt.clock = clock 70 bt.initTime, bt.lastUpdate = clock.Now(), clock.Now() // Init timestamps 71 for i := range bt.callbackIndex { 72 bt.callbackIndex[i] = -1 73 } 74 bt.capacity = capacity 75 } 76 77 // stop shuts down the balance tracker 78 func (bt *balanceTracker) stop(now mclock.AbsTime) { 79 bt.lock.Lock() 80 defer bt.lock.Unlock() 81 82 bt.stopped = true 83 bt.addBalance(now) 84 bt.negTimeFactor = 0 85 bt.negRequestFactor = 0 86 bt.timeFactor = 0 87 bt.requestFactor = 0 88 if bt.updateEvent != nil { 89 bt.updateEvent.Stop() 90 bt.updateEvent = nil 91 } 92 } 93 94 // balanceToPriority converts a balance to a priority value. Higher priority means 95 // first to disconnect. Positive balance translates to negative priority. If positive 96 // balance is zero then negative balance translates to a positive priority. 97 func (bt *balanceTracker) balanceToPriority(b balance) int64 { 98 if b.pos > 0 { 99 return ^int64(b.pos / bt.capacity) 100 } 101 return int64(b.neg) 102 } 103 104 // reducedBalance estimates the reduced balance at a given time in the fututre based 105 // on the current balance, the time factor and an estimated average request cost per time ratio 106 func (bt *balanceTracker) reducedBalance(at mclock.AbsTime, avgReqCost float64) balance { 107 dt := float64(at - bt.lastUpdate) 108 b := bt.balance 109 if b.pos != 0 { 110 factor := bt.timeFactor + bt.requestFactor*avgReqCost 111 diff := uint64(dt * factor) 112 if diff <= b.pos { 113 b.pos -= diff 114 dt = 0 115 } else { 116 dt -= float64(b.pos) / factor 117 b.pos = 0 118 } 119 } 120 if dt != 0 { 121 factor := bt.negTimeFactor + bt.negRequestFactor*avgReqCost 122 b.neg += uint64(dt * factor) 123 } 124 return b 125 } 126 127 // timeUntil calculates the remaining time needed to reach a given priority level 128 // assuming that no requests are processed until then. If the given level is never 129 // reached then (0, false) is returned. 130 // Note: the function assumes that the balance has been recently updated and 131 // calculates the time starting from the last update. 132 func (bt *balanceTracker) timeUntil(priority int64) (time.Duration, bool) { 133 var dt float64 134 if bt.balance.pos != 0 { 135 if bt.timeFactor < 1e-100 { 136 return 0, false 137 } 138 if priority < 0 { 139 newBalance := uint64(^priority) * bt.capacity 140 if newBalance > bt.balance.pos { 141 return 0, false 142 } 143 dt = float64(bt.balance.pos-newBalance) / bt.timeFactor 144 return time.Duration(dt), true 145 } else { 146 dt = float64(bt.balance.pos) / bt.timeFactor 147 } 148 } else { 149 if priority < 0 { 150 return 0, false 151 } 152 } 153 // if we have a positive balance then dt equals the time needed to get it to zero 154 if uint64(priority) > bt.balance.neg { 155 if bt.negTimeFactor < 1e-100 { 156 return 0, false 157 } 158 dt += float64(uint64(priority)-bt.balance.neg) / bt.negTimeFactor 159 } 160 return time.Duration(dt), true 161 } 162 163 // setCapacity updates the capacity value used for priority calculation 164 func (bt *balanceTracker) setCapacity(capacity uint64) { 165 bt.lock.Lock() 166 defer bt.lock.Unlock() 167 168 bt.capacity = capacity 169 } 170 171 // getPriority returns the actual priority based on the current balance 172 func (bt *balanceTracker) getPriority(now mclock.AbsTime) int64 { 173 bt.lock.Lock() 174 defer bt.lock.Unlock() 175 176 bt.addBalance(now) 177 return bt.balanceToPriority(bt.balance) 178 } 179 180 // estimatedPriority gives an upper estimate for the priority at a given time in the future. 181 // If addReqCost is true then an average request cost per time is assumed that is twice the 182 // average cost per time in the current session. If false, zero request cost is assumed. 183 func (bt *balanceTracker) estimatedPriority(at mclock.AbsTime, addReqCost bool) int64 { 184 bt.lock.Lock() 185 defer bt.lock.Unlock() 186 187 var avgReqCost float64 188 if addReqCost { 189 dt := time.Duration(bt.lastUpdate - bt.initTime) 190 if dt > time.Second { 191 avgReqCost = float64(bt.sumReqCost) * 2 / float64(dt) 192 } 193 } 194 return bt.balanceToPriority(bt.reducedBalance(at, avgReqCost)) 195 } 196 197 // addBalance updates balance based on the time factor 198 func (bt *balanceTracker) addBalance(now mclock.AbsTime) { 199 if now > bt.lastUpdate { 200 bt.balance = bt.reducedBalance(now, 0) 201 bt.lastUpdate = now 202 } 203 } 204 205 // checkCallbacks checks whether the threshold of any of the active callbacks 206 // have been reached and calls them if necessary. It also sets up or updates 207 // a scheduled event to ensure that is will be called again just after the next 208 // threshold has been reached. 209 // Note: checkCallbacks assumes that the balance has been recently updated. 210 func (bt *balanceTracker) checkCallbacks(now mclock.AbsTime) { 211 if bt.callbackCount == 0 { 212 return 213 } 214 pri := bt.balanceToPriority(bt.balance) 215 for bt.callbackCount != 0 && bt.callbacks[bt.callbackCount-1].threshold <= pri { 216 bt.callbackCount-- 217 bt.callbackIndex[bt.callbacks[bt.callbackCount].id] = -1 218 go bt.callbacks[bt.callbackCount].callback() 219 } 220 if bt.callbackCount != 0 { 221 d, ok := bt.timeUntil(bt.callbacks[bt.callbackCount-1].threshold) 222 if !ok { 223 bt.nextUpdate = 0 224 bt.updateAfter(0) 225 return 226 } 227 if bt.nextUpdate == 0 || bt.nextUpdate > now+mclock.AbsTime(d) { 228 if d > time.Second { 229 // Note: if the scheduled update is not in the very near future then we 230 // schedule the update a bit earlier. This way we do need to update a few 231 // extra times but don't need to reschedule every time a processed request 232 // brings the expected firing time a little bit closer. 233 d = ((d - time.Second) * 7 / 8) + time.Second 234 } 235 bt.nextUpdate = now + mclock.AbsTime(d) 236 bt.updateAfter(d) 237 } 238 } else { 239 bt.nextUpdate = 0 240 bt.updateAfter(0) 241 } 242 } 243 244 // updateAfter schedules a balance update and callback check in the future 245 func (bt *balanceTracker) updateAfter(dt time.Duration) { 246 if bt.updateEvent == nil || bt.updateEvent.Stop() { 247 if dt == 0 { 248 bt.updateEvent = nil 249 } else { 250 bt.updateEvent = bt.clock.AfterFunc(dt, func() { 251 bt.lock.Lock() 252 defer bt.lock.Unlock() 253 254 if bt.callbackCount != 0 { 255 now := bt.clock.Now() 256 bt.addBalance(now) 257 bt.checkCallbacks(now) 258 } 259 }) 260 } 261 } 262 } 263 264 // requestCost should be called after serving a request for the given peer 265 func (bt *balanceTracker) requestCost(cost uint64) { 266 bt.lock.Lock() 267 defer bt.lock.Unlock() 268 269 if bt.stopped { 270 return 271 } 272 now := bt.clock.Now() 273 bt.addBalance(now) 274 fcost := float64(cost) 275 276 if bt.balance.pos != 0 { 277 if bt.requestFactor != 0 { 278 c := uint64(fcost * bt.requestFactor) 279 if bt.balance.pos >= c { 280 bt.balance.pos -= c 281 fcost = 0 282 } else { 283 fcost *= 1 - float64(bt.balance.pos)/float64(c) 284 bt.balance.pos = 0 285 } 286 bt.checkCallbacks(now) 287 } else { 288 fcost = 0 289 } 290 } 291 if fcost > 0 { 292 if bt.negRequestFactor != 0 { 293 bt.balance.neg += uint64(fcost * bt.negRequestFactor) 294 bt.checkCallbacks(now) 295 } 296 } 297 bt.sumReqCost += cost 298 } 299 300 // getBalance returns the current positive and negative balance 301 func (bt *balanceTracker) getBalance(now mclock.AbsTime) (uint64, uint64) { 302 bt.lock.Lock() 303 defer bt.lock.Unlock() 304 305 bt.addBalance(now) 306 return bt.balance.pos, bt.balance.neg 307 } 308 309 // setBalance sets the positive and negative balance to the given values 310 func (bt *balanceTracker) setBalance(pos, neg uint64) error { 311 bt.lock.Lock() 312 defer bt.lock.Unlock() 313 314 now := bt.clock.Now() 315 bt.addBalance(now) 316 bt.balance.pos = pos 317 bt.balance.neg = neg 318 bt.checkCallbacks(now) 319 return nil 320 } 321 322 // setFactors sets the price factors. timeFactor is the price of a nanosecond of 323 // connection while requestFactor is the price of a "realCost" unit. 324 func (bt *balanceTracker) setFactors(neg bool, timeFactor, requestFactor float64) { 325 bt.lock.Lock() 326 defer bt.lock.Unlock() 327 328 if bt.stopped { 329 return 330 } 331 now := bt.clock.Now() 332 bt.addBalance(now) 333 if neg { 334 bt.negTimeFactor = timeFactor 335 bt.negRequestFactor = requestFactor 336 } else { 337 bt.timeFactor = timeFactor 338 bt.requestFactor = requestFactor 339 } 340 bt.checkCallbacks(now) 341 } 342 343 // setCallback sets up a one-time callback to be called when priority reaches 344 // the threshold. If it has already reached the threshold the callback is called 345 // immediately. 346 func (bt *balanceTracker) addCallback(id int, threshold int64, callback func()) { 347 bt.lock.Lock() 348 defer bt.lock.Unlock() 349 350 bt.removeCb(id) 351 idx := 0 352 for idx < bt.callbackCount && threshold < bt.callbacks[idx].threshold { 353 idx++ 354 } 355 for i := bt.callbackCount - 1; i >= idx; i-- { 356 bt.callbackIndex[bt.callbacks[i].id]++ 357 bt.callbacks[i+1] = bt.callbacks[i] 358 } 359 bt.callbackCount++ 360 bt.callbackIndex[id] = idx 361 bt.callbacks[idx] = balanceCallback{id, threshold, callback} 362 now := bt.clock.Now() 363 bt.addBalance(now) 364 bt.checkCallbacks(now) 365 } 366 367 // removeCallback removes the given callback and returns true if it was active 368 func (bt *balanceTracker) removeCallback(id int) bool { 369 bt.lock.Lock() 370 defer bt.lock.Unlock() 371 372 return bt.removeCb(id) 373 } 374 375 // removeCb removes the given callback and returns true if it was active 376 // Note: should be called while bt.lock is held 377 func (bt *balanceTracker) removeCb(id int) bool { 378 idx := bt.callbackIndex[id] 379 if idx == -1 { 380 return false 381 } 382 bt.callbackIndex[id] = -1 383 for i := idx; i < bt.callbackCount-1; i++ { 384 bt.callbackIndex[bt.callbacks[i+1].id]-- 385 bt.callbacks[i] = bt.callbacks[i+1] 386 } 387 bt.callbackCount-- 388 return true 389 }