github.com/cerc-io/go-ethereum@v1.9.7/les/flowcontrol/control.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 flowcontrol implements a client side flow control mechanism 18 package flowcontrol 19 20 import ( 21 "fmt" 22 "sync" 23 "time" 24 25 "github.com/ethereum/go-ethereum/common/mclock" 26 "github.com/ethereum/go-ethereum/log" 27 ) 28 29 const ( 30 // fcTimeConst is the time constant applied for MinRecharge during linear 31 // buffer recharge period 32 fcTimeConst = time.Millisecond 33 // DecParamDelay is applied at server side when decreasing capacity in order to 34 // avoid a buffer underrun error due to requests sent by the client before 35 // receiving the capacity update announcement 36 DecParamDelay = time.Second * 2 37 // keepLogs is the duration of keeping logs; logging is not used if zero 38 keepLogs = 0 39 ) 40 41 // ServerParams are the flow control parameters specified by a server for a client 42 // 43 // Note: a server can assign different amounts of capacity to each client by giving 44 // different parameters to them. 45 type ServerParams struct { 46 BufLimit, MinRecharge uint64 47 } 48 49 // scheduledUpdate represents a delayed flow control parameter update 50 type scheduledUpdate struct { 51 time mclock.AbsTime 52 params ServerParams 53 } 54 55 // ClientNode is the flow control system's representation of a client 56 // (used in server mode only) 57 type ClientNode struct { 58 params ServerParams 59 bufValue int64 60 lastTime mclock.AbsTime 61 updateSchedule []scheduledUpdate 62 sumCost uint64 // sum of req costs received from this client 63 accepted map[uint64]uint64 // value = sumCost after accepting the given req 64 connected bool 65 lock sync.Mutex 66 cm *ClientManager 67 log *logger 68 cmNodeFields 69 } 70 71 // NewClientNode returns a new ClientNode 72 func NewClientNode(cm *ClientManager, params ServerParams) *ClientNode { 73 node := &ClientNode{ 74 cm: cm, 75 params: params, 76 bufValue: int64(params.BufLimit), 77 lastTime: cm.clock.Now(), 78 accepted: make(map[uint64]uint64), 79 connected: true, 80 } 81 if keepLogs > 0 { 82 node.log = newLogger(keepLogs) 83 } 84 cm.connect(node) 85 return node 86 } 87 88 // Disconnect should be called when a client is disconnected 89 func (node *ClientNode) Disconnect() { 90 node.lock.Lock() 91 defer node.lock.Unlock() 92 93 node.connected = false 94 node.cm.disconnect(node) 95 } 96 97 // BufferStatus returns the current buffer value and limit 98 func (node *ClientNode) BufferStatus() (uint64, uint64) { 99 node.lock.Lock() 100 defer node.lock.Unlock() 101 102 if !node.connected { 103 return 0, 0 104 } 105 now := node.cm.clock.Now() 106 node.update(now) 107 node.cm.updateBuffer(node, 0, now) 108 bv := node.bufValue 109 if bv < 0 { 110 bv = 0 111 } 112 return uint64(bv), node.params.BufLimit 113 } 114 115 // OneTimeCost subtracts the given amount from the node's buffer. 116 // 117 // Note: this call can take the buffer into the negative region internally. 118 // In this case zero buffer value is returned by exported calls and no requests 119 // are accepted. 120 func (node *ClientNode) OneTimeCost(cost uint64) { 121 node.lock.Lock() 122 defer node.lock.Unlock() 123 124 now := node.cm.clock.Now() 125 node.update(now) 126 node.bufValue -= int64(cost) 127 node.cm.updateBuffer(node, -int64(cost), now) 128 } 129 130 // Freeze notifies the client manager about a client freeze event in which case 131 // the total capacity allowance is slightly reduced. 132 func (node *ClientNode) Freeze() { 133 node.lock.Lock() 134 frozenCap := node.params.MinRecharge 135 node.lock.Unlock() 136 node.cm.reduceTotalCapacity(frozenCap) 137 } 138 139 // update recalculates the buffer value at a specified time while also performing 140 // scheduled flow control parameter updates if necessary 141 func (node *ClientNode) update(now mclock.AbsTime) { 142 for len(node.updateSchedule) > 0 && node.updateSchedule[0].time <= now { 143 node.recalcBV(node.updateSchedule[0].time) 144 node.updateParams(node.updateSchedule[0].params, now) 145 node.updateSchedule = node.updateSchedule[1:] 146 } 147 node.recalcBV(now) 148 } 149 150 // recalcBV recalculates the buffer value at a specified time 151 func (node *ClientNode) recalcBV(now mclock.AbsTime) { 152 dt := uint64(now - node.lastTime) 153 if now < node.lastTime { 154 dt = 0 155 } 156 node.bufValue += int64(node.params.MinRecharge * dt / uint64(fcTimeConst)) 157 if node.bufValue > int64(node.params.BufLimit) { 158 node.bufValue = int64(node.params.BufLimit) 159 } 160 if node.log != nil { 161 node.log.add(now, fmt.Sprintf("updated bv=%d MRR=%d BufLimit=%d", node.bufValue, node.params.MinRecharge, node.params.BufLimit)) 162 } 163 node.lastTime = now 164 } 165 166 // UpdateParams updates the flow control parameters of a client node 167 func (node *ClientNode) UpdateParams(params ServerParams) { 168 node.lock.Lock() 169 defer node.lock.Unlock() 170 171 now := node.cm.clock.Now() 172 node.update(now) 173 if params.MinRecharge >= node.params.MinRecharge { 174 node.updateSchedule = nil 175 node.updateParams(params, now) 176 } else { 177 for i, s := range node.updateSchedule { 178 if params.MinRecharge >= s.params.MinRecharge { 179 s.params = params 180 node.updateSchedule = node.updateSchedule[:i+1] 181 return 182 } 183 } 184 node.updateSchedule = append(node.updateSchedule, scheduledUpdate{time: now + mclock.AbsTime(DecParamDelay), params: params}) 185 } 186 } 187 188 // updateParams updates the flow control parameters of the node 189 func (node *ClientNode) updateParams(params ServerParams, now mclock.AbsTime) { 190 diff := int64(params.BufLimit - node.params.BufLimit) 191 if diff > 0 { 192 node.bufValue += diff 193 } else if node.bufValue > int64(params.BufLimit) { 194 node.bufValue = int64(params.BufLimit) 195 } 196 node.cm.updateParams(node, params, now) 197 } 198 199 // AcceptRequest returns whether a new request can be accepted and the missing 200 // buffer amount if it was rejected due to a buffer underrun. If accepted, maxCost 201 // is deducted from the flow control buffer. 202 func (node *ClientNode) AcceptRequest(reqID, index, maxCost uint64) (accepted bool, bufShort uint64, priority int64) { 203 node.lock.Lock() 204 defer node.lock.Unlock() 205 206 now := node.cm.clock.Now() 207 node.update(now) 208 if int64(maxCost) > node.bufValue { 209 if node.log != nil { 210 node.log.add(now, fmt.Sprintf("rejected reqID=%d bv=%d maxCost=%d", reqID, node.bufValue, maxCost)) 211 node.log.dump(now) 212 } 213 return false, maxCost - uint64(node.bufValue), 0 214 } 215 node.bufValue -= int64(maxCost) 216 node.sumCost += maxCost 217 if node.log != nil { 218 node.log.add(now, fmt.Sprintf("accepted reqID=%d bv=%d maxCost=%d sumCost=%d", reqID, node.bufValue, maxCost, node.sumCost)) 219 } 220 node.accepted[index] = node.sumCost 221 return true, 0, node.cm.accepted(node, maxCost, now) 222 } 223 224 // RequestProcessed should be called when the request has been processed 225 func (node *ClientNode) RequestProcessed(reqID, index, maxCost, realCost uint64) uint64 { 226 node.lock.Lock() 227 defer node.lock.Unlock() 228 229 now := node.cm.clock.Now() 230 node.update(now) 231 node.cm.processed(node, maxCost, realCost, now) 232 bv := node.bufValue + int64(node.sumCost-node.accepted[index]) 233 if node.log != nil { 234 node.log.add(now, fmt.Sprintf("processed reqID=%d bv=%d maxCost=%d realCost=%d sumCost=%d oldSumCost=%d reportedBV=%d", reqID, node.bufValue, maxCost, realCost, node.sumCost, node.accepted[index], bv)) 235 } 236 delete(node.accepted, index) 237 if bv < 0 { 238 return 0 239 } 240 return uint64(bv) 241 } 242 243 // ServerNode is the flow control system's representation of a server 244 // (used in client mode only) 245 type ServerNode struct { 246 clock mclock.Clock 247 bufEstimate uint64 248 bufRecharge bool 249 lastTime mclock.AbsTime 250 params ServerParams 251 sumCost uint64 // sum of req costs sent to this server 252 pending map[uint64]uint64 // value = sumCost after sending the given req 253 log *logger 254 lock sync.RWMutex 255 } 256 257 // NewServerNode returns a new ServerNode 258 func NewServerNode(params ServerParams, clock mclock.Clock) *ServerNode { 259 node := &ServerNode{ 260 clock: clock, 261 bufEstimate: params.BufLimit, 262 bufRecharge: false, 263 lastTime: clock.Now(), 264 params: params, 265 pending: make(map[uint64]uint64), 266 } 267 if keepLogs > 0 { 268 node.log = newLogger(keepLogs) 269 } 270 return node 271 } 272 273 // UpdateParams updates the flow control parameters of the node 274 func (node *ServerNode) UpdateParams(params ServerParams) { 275 node.lock.Lock() 276 defer node.lock.Unlock() 277 278 node.recalcBLE(mclock.Now()) 279 if params.BufLimit > node.params.BufLimit { 280 node.bufEstimate += params.BufLimit - node.params.BufLimit 281 } else { 282 if node.bufEstimate > params.BufLimit { 283 node.bufEstimate = params.BufLimit 284 } 285 } 286 node.params = params 287 } 288 289 // recalcBLE recalculates the lowest estimate for the client's buffer value at 290 // the given server at the specified time 291 func (node *ServerNode) recalcBLE(now mclock.AbsTime) { 292 if now < node.lastTime { 293 return 294 } 295 if node.bufRecharge { 296 dt := uint64(now - node.lastTime) 297 node.bufEstimate += node.params.MinRecharge * dt / uint64(fcTimeConst) 298 if node.bufEstimate >= node.params.BufLimit { 299 node.bufEstimate = node.params.BufLimit 300 node.bufRecharge = false 301 } 302 } 303 node.lastTime = now 304 if node.log != nil { 305 node.log.add(now, fmt.Sprintf("updated bufEst=%d MRR=%d BufLimit=%d", node.bufEstimate, node.params.MinRecharge, node.params.BufLimit)) 306 } 307 } 308 309 // safetyMargin is added to the flow control waiting time when estimated buffer value is low 310 const safetyMargin = time.Millisecond 311 312 // CanSend returns the minimum waiting time required before sending a request 313 // with the given maximum estimated cost. Second return value is the relative 314 // estimated buffer level after sending the request (divided by BufLimit). 315 func (node *ServerNode) CanSend(maxCost uint64) (time.Duration, float64) { 316 node.lock.RLock() 317 defer node.lock.RUnlock() 318 319 now := node.clock.Now() 320 node.recalcBLE(now) 321 maxCost += uint64(safetyMargin) * node.params.MinRecharge / uint64(fcTimeConst) 322 if maxCost > node.params.BufLimit { 323 maxCost = node.params.BufLimit 324 } 325 if node.bufEstimate >= maxCost { 326 relBuf := float64(node.bufEstimate-maxCost) / float64(node.params.BufLimit) 327 if node.log != nil { 328 node.log.add(now, fmt.Sprintf("canSend bufEst=%d maxCost=%d true relBuf=%f", node.bufEstimate, maxCost, relBuf)) 329 } 330 return 0, relBuf 331 } 332 timeLeft := time.Duration((maxCost - node.bufEstimate) * uint64(fcTimeConst) / node.params.MinRecharge) 333 if node.log != nil { 334 node.log.add(now, fmt.Sprintf("canSend bufEst=%d maxCost=%d false timeLeft=%v", node.bufEstimate, maxCost, timeLeft)) 335 } 336 return timeLeft, 0 337 } 338 339 // QueuedRequest should be called when the request has been assigned to the given 340 // server node, before putting it in the send queue. It is mandatory that requests 341 // are sent in the same order as the QueuedRequest calls are made. 342 func (node *ServerNode) QueuedRequest(reqID, maxCost uint64) { 343 node.lock.Lock() 344 defer node.lock.Unlock() 345 346 now := node.clock.Now() 347 node.recalcBLE(now) 348 // Note: we do not know when requests actually arrive to the server so bufRecharge 349 // is not turned on here if buffer was full; in this case it is going to be turned 350 // on by the first reply's bufValue feedback 351 if node.bufEstimate >= maxCost { 352 node.bufEstimate -= maxCost 353 } else { 354 log.Error("Queued request with insufficient buffer estimate") 355 node.bufEstimate = 0 356 } 357 node.sumCost += maxCost 358 node.pending[reqID] = node.sumCost 359 if node.log != nil { 360 node.log.add(now, fmt.Sprintf("queued reqID=%d bufEst=%d maxCost=%d sumCost=%d", reqID, node.bufEstimate, maxCost, node.sumCost)) 361 } 362 } 363 364 // ReceivedReply adjusts estimated buffer value according to the value included in 365 // the latest request reply. 366 func (node *ServerNode) ReceivedReply(reqID, bv uint64) { 367 node.lock.Lock() 368 defer node.lock.Unlock() 369 370 now := node.clock.Now() 371 node.recalcBLE(now) 372 if bv > node.params.BufLimit { 373 bv = node.params.BufLimit 374 } 375 sc, ok := node.pending[reqID] 376 if !ok { 377 return 378 } 379 delete(node.pending, reqID) 380 cc := node.sumCost - sc 381 newEstimate := uint64(0) 382 if bv > cc { 383 newEstimate = bv - cc 384 } 385 if newEstimate > node.bufEstimate { 386 // Note: we never reduce the buffer estimate based on the reported value because 387 // this can only happen because of the delayed delivery of the latest reply. 388 // The lowest estimate based on the previous reply can still be considered valid. 389 node.bufEstimate = newEstimate 390 } 391 392 node.bufRecharge = node.bufEstimate < node.params.BufLimit 393 node.lastTime = now 394 if node.log != nil { 395 node.log.add(now, fmt.Sprintf("received reqID=%d bufEst=%d reportedBv=%d sumCost=%d oldSumCost=%d", reqID, node.bufEstimate, bv, node.sumCost, sc)) 396 } 397 } 398 399 // ResumeFreeze cleans all pending requests and sets the buffer estimate to the 400 // reported value after resuming from a frozen state 401 func (node *ServerNode) ResumeFreeze(bv uint64) { 402 node.lock.Lock() 403 defer node.lock.Unlock() 404 405 for reqID := range node.pending { 406 delete(node.pending, reqID) 407 } 408 now := node.clock.Now() 409 node.recalcBLE(now) 410 if bv > node.params.BufLimit { 411 bv = node.params.BufLimit 412 } 413 node.bufEstimate = bv 414 node.bufRecharge = node.bufEstimate < node.params.BufLimit 415 node.lastTime = now 416 if node.log != nil { 417 node.log.add(now, fmt.Sprintf("unfreeze bv=%d sumCost=%d", bv, node.sumCost)) 418 } 419 } 420 421 // DumpLogs dumps the event log if logging is used 422 func (node *ServerNode) DumpLogs() { 423 node.lock.Lock() 424 defer node.lock.Unlock() 425 426 if node.log != nil { 427 node.log.dump(node.clock.Now()) 428 } 429 }