github.com/jimmyx0x/go-ethereum@v1.10.28/les/servingqueue.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 "sort" 21 "sync" 22 "sync/atomic" 23 24 "github.com/ethereum/go-ethereum/common/mclock" 25 "github.com/ethereum/go-ethereum/common/prque" 26 ) 27 28 // servingQueue allows running tasks in a limited number of threads and puts the 29 // waiting tasks in a priority queue 30 type servingQueue struct { 31 recentTime, queuedTime, servingTimeDiff uint64 32 burstLimit, burstDropLimit uint64 33 burstDecRate float64 34 lastUpdate mclock.AbsTime 35 36 queueAddCh, queueBestCh chan *servingTask 37 stopThreadCh, quit chan struct{} 38 setThreadsCh chan int 39 40 wg sync.WaitGroup 41 threadCount int // number of currently running threads 42 queue *prque.Prque // priority queue for waiting or suspended tasks 43 best *servingTask // the highest priority task (not included in the queue) 44 suspendBias int64 // priority bias against suspending an already running task 45 } 46 47 // servingTask represents a request serving task. Tasks can be implemented to 48 // run in multiple steps, allowing the serving queue to suspend execution between 49 // steps if higher priority tasks are entered. The creator of the task should 50 // set the following fields: 51 // 52 // - priority: greater value means higher priority; values can wrap around the int64 range 53 // - run: execute a single step; return true if finished 54 // - after: executed after run finishes or returns an error, receives the total serving time 55 type servingTask struct { 56 sq *servingQueue 57 servingTime, timeAdded, maxTime, expTime uint64 58 peer *clientPeer 59 priority int64 60 biasAdded bool 61 token runToken 62 tokenCh chan runToken 63 } 64 65 // runToken received by servingTask.start allows the task to run. Closing the 66 // channel by servingTask.stop signals the thread controller to allow a new task 67 // to start running. 68 type runToken chan struct{} 69 70 // start blocks until the task can start and returns true if it is allowed to run. 71 // Returning false means that the task should be cancelled. 72 func (t *servingTask) start() bool { 73 if t.peer.isFrozen() { 74 return false 75 } 76 t.tokenCh = make(chan runToken, 1) 77 select { 78 case t.sq.queueAddCh <- t: 79 case <-t.sq.quit: 80 return false 81 } 82 select { 83 case t.token = <-t.tokenCh: 84 case <-t.sq.quit: 85 return false 86 } 87 if t.token == nil { 88 return false 89 } 90 t.servingTime -= uint64(mclock.Now()) 91 return true 92 } 93 94 // done signals the thread controller about the task being finished and returns 95 // the total serving time of the task in nanoseconds. 96 func (t *servingTask) done() uint64 { 97 t.servingTime += uint64(mclock.Now()) 98 close(t.token) 99 diff := t.servingTime - t.timeAdded 100 t.timeAdded = t.servingTime 101 if t.expTime > diff { 102 t.expTime -= diff 103 atomic.AddUint64(&t.sq.servingTimeDiff, t.expTime) 104 } else { 105 t.expTime = 0 106 } 107 return t.servingTime 108 } 109 110 // waitOrStop can be called during the execution of the task. It blocks if there 111 // is a higher priority task waiting (a bias is applied in favor of the currently 112 // running task). Returning true means that the execution can be resumed. False 113 // means the task should be cancelled. 114 func (t *servingTask) waitOrStop() bool { 115 t.done() 116 if !t.biasAdded { 117 t.priority += t.sq.suspendBias 118 t.biasAdded = true 119 } 120 return t.start() 121 } 122 123 // newServingQueue returns a new servingQueue 124 func newServingQueue(suspendBias int64, utilTarget float64) *servingQueue { 125 sq := &servingQueue{ 126 queue: prque.NewWrapAround(nil), 127 suspendBias: suspendBias, 128 queueAddCh: make(chan *servingTask, 100), 129 queueBestCh: make(chan *servingTask), 130 stopThreadCh: make(chan struct{}), 131 quit: make(chan struct{}), 132 setThreadsCh: make(chan int, 10), 133 burstLimit: uint64(utilTarget * bufLimitRatio * 1200000), 134 burstDropLimit: uint64(utilTarget * bufLimitRatio * 1000000), 135 burstDecRate: utilTarget, 136 lastUpdate: mclock.Now(), 137 } 138 sq.wg.Add(2) 139 go sq.queueLoop() 140 go sq.threadCountLoop() 141 return sq 142 } 143 144 // newTask creates a new task with the given priority 145 func (sq *servingQueue) newTask(peer *clientPeer, maxTime uint64, priority int64) *servingTask { 146 return &servingTask{ 147 sq: sq, 148 peer: peer, 149 maxTime: maxTime, 150 expTime: maxTime, 151 priority: priority, 152 } 153 } 154 155 // threadController is started in multiple goroutines and controls the execution 156 // of tasks. The number of active thread controllers equals the allowed number of 157 // concurrently running threads. It tries to fetch the highest priority queued 158 // task first. If there are no queued tasks waiting then it can directly catch 159 // run tokens from the token channel and allow the corresponding tasks to run 160 // without entering the priority queue. 161 func (sq *servingQueue) threadController() { 162 defer sq.wg.Done() 163 for { 164 token := make(runToken) 165 select { 166 case best := <-sq.queueBestCh: 167 best.tokenCh <- token 168 case <-sq.stopThreadCh: 169 return 170 case <-sq.quit: 171 return 172 } 173 select { 174 case <-sq.stopThreadCh: 175 return 176 case <-sq.quit: 177 return 178 case <-token: 179 } 180 } 181 } 182 183 type ( 184 // peerTasks lists the tasks received from a given peer when selecting peers to freeze 185 peerTasks struct { 186 peer *clientPeer 187 list []*servingTask 188 sumTime uint64 189 priority float64 190 } 191 // peerList is a sortable list of peerTasks 192 peerList []*peerTasks 193 ) 194 195 func (l peerList) Len() int { 196 return len(l) 197 } 198 199 func (l peerList) Less(i, j int) bool { 200 return l[i].priority < l[j].priority 201 } 202 203 func (l peerList) Swap(i, j int) { 204 l[i], l[j] = l[j], l[i] 205 } 206 207 // freezePeers selects the peers with the worst priority queued tasks and freezes 208 // them until burstTime goes under burstDropLimit or all peers are frozen 209 func (sq *servingQueue) freezePeers() { 210 peerMap := make(map[*clientPeer]*peerTasks) 211 var peerList peerList 212 if sq.best != nil { 213 sq.queue.Push(sq.best, sq.best.priority) 214 } 215 sq.best = nil 216 for sq.queue.Size() > 0 { 217 task := sq.queue.PopItem().(*servingTask) 218 tasks := peerMap[task.peer] 219 if tasks == nil { 220 bufValue, bufLimit := task.peer.fcClient.BufferStatus() 221 if bufLimit < 1 { 222 bufLimit = 1 223 } 224 tasks = &peerTasks{ 225 peer: task.peer, 226 priority: float64(bufValue) / float64(bufLimit), // lower value comes first 227 } 228 peerMap[task.peer] = tasks 229 peerList = append(peerList, tasks) 230 } 231 tasks.list = append(tasks.list, task) 232 tasks.sumTime += task.expTime 233 } 234 sort.Sort(peerList) 235 drop := true 236 for _, tasks := range peerList { 237 if drop { 238 tasks.peer.freeze() 239 tasks.peer.fcClient.Freeze() 240 sq.queuedTime -= tasks.sumTime 241 sqQueuedGauge.Update(int64(sq.queuedTime)) 242 clientFreezeMeter.Mark(1) 243 drop = sq.recentTime+sq.queuedTime > sq.burstDropLimit 244 for _, task := range tasks.list { 245 task.tokenCh <- nil 246 } 247 } else { 248 for _, task := range tasks.list { 249 sq.queue.Push(task, task.priority) 250 } 251 } 252 } 253 if sq.queue.Size() > 0 { 254 sq.best = sq.queue.PopItem().(*servingTask) 255 } 256 } 257 258 // updateRecentTime recalculates the recent serving time value 259 func (sq *servingQueue) updateRecentTime() { 260 subTime := atomic.SwapUint64(&sq.servingTimeDiff, 0) 261 now := mclock.Now() 262 dt := now - sq.lastUpdate 263 sq.lastUpdate = now 264 if dt > 0 { 265 subTime += uint64(float64(dt) * sq.burstDecRate) 266 } 267 if sq.recentTime > subTime { 268 sq.recentTime -= subTime 269 } else { 270 sq.recentTime = 0 271 } 272 } 273 274 // addTask inserts a task into the priority queue 275 func (sq *servingQueue) addTask(task *servingTask) { 276 if sq.best == nil { 277 sq.best = task 278 } else if task.priority-sq.best.priority > 0 { 279 sq.queue.Push(sq.best, sq.best.priority) 280 sq.best = task 281 } else { 282 sq.queue.Push(task, task.priority) 283 } 284 sq.updateRecentTime() 285 sq.queuedTime += task.expTime 286 sqServedGauge.Update(int64(sq.recentTime)) 287 sqQueuedGauge.Update(int64(sq.queuedTime)) 288 if sq.recentTime+sq.queuedTime > sq.burstLimit { 289 sq.freezePeers() 290 } 291 } 292 293 // queueLoop is an event loop running in a goroutine. It receives tasks from queueAddCh 294 // and always tries to send the highest priority task to queueBestCh. Successfully sent 295 // tasks are removed from the queue. 296 func (sq *servingQueue) queueLoop() { 297 defer sq.wg.Done() 298 for { 299 if sq.best != nil { 300 expTime := sq.best.expTime 301 select { 302 case task := <-sq.queueAddCh: 303 sq.addTask(task) 304 case sq.queueBestCh <- sq.best: 305 sq.updateRecentTime() 306 sq.queuedTime -= expTime 307 sq.recentTime += expTime 308 sqServedGauge.Update(int64(sq.recentTime)) 309 sqQueuedGauge.Update(int64(sq.queuedTime)) 310 if sq.queue.Size() == 0 { 311 sq.best = nil 312 } else { 313 sq.best, _ = sq.queue.PopItem().(*servingTask) 314 } 315 case <-sq.quit: 316 return 317 } 318 } else { 319 select { 320 case task := <-sq.queueAddCh: 321 sq.addTask(task) 322 case <-sq.quit: 323 return 324 } 325 } 326 } 327 } 328 329 // threadCountLoop is an event loop running in a goroutine. It adjusts the number 330 // of active thread controller goroutines. 331 func (sq *servingQueue) threadCountLoop() { 332 var threadCountTarget int 333 defer sq.wg.Done() 334 for { 335 for threadCountTarget > sq.threadCount { 336 sq.wg.Add(1) 337 go sq.threadController() 338 sq.threadCount++ 339 } 340 if threadCountTarget < sq.threadCount { 341 select { 342 case threadCountTarget = <-sq.setThreadsCh: 343 case sq.stopThreadCh <- struct{}{}: 344 sq.threadCount-- 345 case <-sq.quit: 346 return 347 } 348 } else { 349 select { 350 case threadCountTarget = <-sq.setThreadsCh: 351 case <-sq.quit: 352 return 353 } 354 } 355 } 356 } 357 358 // setThreads sets the allowed processing thread count, suspending tasks as soon as 359 // possible if necessary. 360 func (sq *servingQueue) setThreads(threadCount int) { 361 select { 362 case sq.setThreadsCh <- threadCount: 363 case <-sq.quit: 364 return 365 } 366 } 367 368 // stop stops task processing as soon as possible and shuts down the serving queue. 369 func (sq *servingQueue) stop() { 370 close(sq.quit) 371 sq.wg.Wait() 372 }