code.gitea.io/gitea@v1.19.3/modules/queue/queue_bytefifo.go (about) 1 // Copyright 2020 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package queue 5 6 import ( 7 "context" 8 "fmt" 9 "runtime/pprof" 10 "sync" 11 "sync/atomic" 12 "time" 13 14 "code.gitea.io/gitea/modules/json" 15 "code.gitea.io/gitea/modules/log" 16 "code.gitea.io/gitea/modules/util" 17 ) 18 19 // ByteFIFOQueueConfiguration is the configuration for a ByteFIFOQueue 20 type ByteFIFOQueueConfiguration struct { 21 WorkerPoolConfiguration 22 Workers int 23 WaitOnEmpty bool 24 } 25 26 var _ Queue = &ByteFIFOQueue{} 27 28 // ByteFIFOQueue is a Queue formed from a ByteFIFO and WorkerPool 29 type ByteFIFOQueue struct { 30 *WorkerPool 31 byteFIFO ByteFIFO 32 typ Type 33 shutdownCtx context.Context 34 shutdownCtxCancel context.CancelFunc 35 terminateCtx context.Context 36 terminateCtxCancel context.CancelFunc 37 exemplar interface{} 38 workers int 39 name string 40 lock sync.Mutex 41 waitOnEmpty bool 42 pushed chan struct{} 43 } 44 45 // NewByteFIFOQueue creates a new ByteFIFOQueue 46 func NewByteFIFOQueue(typ Type, byteFIFO ByteFIFO, handle HandlerFunc, cfg, exemplar interface{}) (*ByteFIFOQueue, error) { 47 configInterface, err := toConfig(ByteFIFOQueueConfiguration{}, cfg) 48 if err != nil { 49 return nil, err 50 } 51 config := configInterface.(ByteFIFOQueueConfiguration) 52 53 terminateCtx, terminateCtxCancel := context.WithCancel(context.Background()) 54 shutdownCtx, shutdownCtxCancel := context.WithCancel(terminateCtx) 55 56 q := &ByteFIFOQueue{ 57 byteFIFO: byteFIFO, 58 typ: typ, 59 shutdownCtx: shutdownCtx, 60 shutdownCtxCancel: shutdownCtxCancel, 61 terminateCtx: terminateCtx, 62 terminateCtxCancel: terminateCtxCancel, 63 exemplar: exemplar, 64 workers: config.Workers, 65 name: config.Name, 66 waitOnEmpty: config.WaitOnEmpty, 67 pushed: make(chan struct{}, 1), 68 } 69 q.WorkerPool = NewWorkerPool(func(data ...Data) (failed []Data) { 70 for _, unhandled := range handle(data...) { 71 if fail := q.PushBack(unhandled); fail != nil { 72 failed = append(failed, fail) 73 } 74 } 75 return failed 76 }, config.WorkerPoolConfiguration) 77 78 return q, nil 79 } 80 81 // Name returns the name of this queue 82 func (q *ByteFIFOQueue) Name() string { 83 return q.name 84 } 85 86 // Push pushes data to the fifo 87 func (q *ByteFIFOQueue) Push(data Data) error { 88 return q.PushFunc(data, nil) 89 } 90 91 // PushBack pushes data to the fifo 92 func (q *ByteFIFOQueue) PushBack(data Data) error { 93 if !assignableTo(data, q.exemplar) { 94 return fmt.Errorf("unable to assign data: %v to same type as exemplar: %v in %s", data, q.exemplar, q.name) 95 } 96 bs, err := json.Marshal(data) 97 if err != nil { 98 return err 99 } 100 defer func() { 101 select { 102 case q.pushed <- struct{}{}: 103 default: 104 } 105 }() 106 return q.byteFIFO.PushBack(q.terminateCtx, bs) 107 } 108 109 // PushFunc pushes data to the fifo 110 func (q *ByteFIFOQueue) PushFunc(data Data, fn func() error) error { 111 if !assignableTo(data, q.exemplar) { 112 return fmt.Errorf("unable to assign data: %v to same type as exemplar: %v in %s", data, q.exemplar, q.name) 113 } 114 bs, err := json.Marshal(data) 115 if err != nil { 116 return err 117 } 118 defer func() { 119 select { 120 case q.pushed <- struct{}{}: 121 default: 122 } 123 }() 124 return q.byteFIFO.PushFunc(q.terminateCtx, bs, fn) 125 } 126 127 // IsEmpty checks if the queue is empty 128 func (q *ByteFIFOQueue) IsEmpty() bool { 129 q.lock.Lock() 130 defer q.lock.Unlock() 131 if !q.WorkerPool.IsEmpty() { 132 return false 133 } 134 return q.byteFIFO.Len(q.terminateCtx) == 0 135 } 136 137 // NumberInQueue returns the number in the queue 138 func (q *ByteFIFOQueue) NumberInQueue() int64 { 139 q.lock.Lock() 140 defer q.lock.Unlock() 141 return q.byteFIFO.Len(q.terminateCtx) + q.WorkerPool.NumberInQueue() 142 } 143 144 // Flush flushes the ByteFIFOQueue 145 func (q *ByteFIFOQueue) Flush(timeout time.Duration) error { 146 select { 147 case q.pushed <- struct{}{}: 148 default: 149 } 150 return q.WorkerPool.Flush(timeout) 151 } 152 153 // Run runs the bytefifo queue 154 func (q *ByteFIFOQueue) Run(atShutdown, atTerminate func(func())) { 155 pprof.SetGoroutineLabels(q.baseCtx) 156 atShutdown(q.Shutdown) 157 atTerminate(q.Terminate) 158 log.Debug("%s: %s Starting", q.typ, q.name) 159 160 _ = q.AddWorkers(q.workers, 0) 161 162 log.Trace("%s: %s Now running", q.typ, q.name) 163 q.readToChan() 164 165 <-q.shutdownCtx.Done() 166 log.Trace("%s: %s Waiting til done", q.typ, q.name) 167 q.Wait() 168 169 log.Trace("%s: %s Waiting til cleaned", q.typ, q.name) 170 q.CleanUp(q.terminateCtx) 171 q.terminateCtxCancel() 172 } 173 174 const maxBackOffTime = time.Second * 3 175 176 func (q *ByteFIFOQueue) readToChan() { 177 // handle quick cancels 178 select { 179 case <-q.shutdownCtx.Done(): 180 // tell the pool to shutdown. 181 q.baseCtxCancel() 182 return 183 default: 184 } 185 186 // Default backoff values 187 backOffTime := time.Millisecond * 100 188 backOffTimer := time.NewTimer(0) 189 util.StopTimer(backOffTimer) 190 191 paused, _ := q.IsPausedIsResumed() 192 193 loop: 194 for { 195 select { 196 case <-paused: 197 log.Trace("Queue %s pausing", q.name) 198 _, resumed := q.IsPausedIsResumed() 199 200 select { 201 case <-resumed: 202 paused, _ = q.IsPausedIsResumed() 203 log.Trace("Queue %s resuming", q.name) 204 if q.HasNoWorkerScaling() { 205 log.Warn( 206 "Queue: %s is configured to be non-scaling and has no workers - this configuration is likely incorrect.\n"+ 207 "The queue will be paused to prevent data-loss with the assumption that you will add workers and unpause as required.", q.name) 208 q.Pause() 209 continue loop 210 } 211 case <-q.shutdownCtx.Done(): 212 // tell the pool to shutdown. 213 q.baseCtxCancel() 214 return 215 case data, ok := <-q.dataChan: 216 if !ok { 217 return 218 } 219 if err := q.PushBack(data); err != nil { 220 log.Error("Unable to push back data into queue %s", q.name) 221 } 222 atomic.AddInt64(&q.numInQueue, -1) 223 } 224 default: 225 } 226 227 // empty the pushed channel 228 select { 229 case <-q.pushed: 230 default: 231 } 232 233 err := q.doPop() 234 235 util.StopTimer(backOffTimer) 236 237 if err != nil { 238 if err == errQueueEmpty && q.waitOnEmpty { 239 log.Trace("%s: %s Waiting on Empty", q.typ, q.name) 240 241 // reset the backoff time but don't set the timer 242 backOffTime = 100 * time.Millisecond 243 } else if err == errUnmarshal { 244 // reset the timer and backoff 245 backOffTime = 100 * time.Millisecond 246 backOffTimer.Reset(backOffTime) 247 } else { 248 // backoff 249 backOffTimer.Reset(backOffTime) 250 } 251 252 // Need to Backoff 253 select { 254 case <-q.shutdownCtx.Done(): 255 // Oops we've been shutdown whilst backing off 256 // Make sure the worker pool is shutdown too 257 q.baseCtxCancel() 258 return 259 case <-q.pushed: 260 // Data has been pushed to the fifo (or flush has been called) 261 // reset the backoff time 262 backOffTime = 100 * time.Millisecond 263 continue loop 264 case <-backOffTimer.C: 265 // Calculate the next backoff time 266 backOffTime += backOffTime / 2 267 if backOffTime > maxBackOffTime { 268 backOffTime = maxBackOffTime 269 } 270 continue loop 271 } 272 } 273 274 // Reset the backoff time 275 backOffTime = 100 * time.Millisecond 276 277 select { 278 case <-q.shutdownCtx.Done(): 279 // Oops we've been shutdown 280 // Make sure the worker pool is shutdown too 281 q.baseCtxCancel() 282 return 283 default: 284 continue loop 285 } 286 } 287 } 288 289 var ( 290 errQueueEmpty = fmt.Errorf("empty queue") 291 errEmptyBytes = fmt.Errorf("empty bytes") 292 errUnmarshal = fmt.Errorf("failed to unmarshal") 293 ) 294 295 func (q *ByteFIFOQueue) doPop() error { 296 q.lock.Lock() 297 defer q.lock.Unlock() 298 bs, err := q.byteFIFO.Pop(q.shutdownCtx) 299 if err != nil { 300 if err == context.Canceled { 301 q.baseCtxCancel() 302 return err 303 } 304 log.Error("%s: %s Error on Pop: %v", q.typ, q.name, err) 305 return err 306 } 307 if len(bs) == 0 { 308 if q.waitOnEmpty && q.byteFIFO.Len(q.shutdownCtx) == 0 { 309 return errQueueEmpty 310 } 311 return errEmptyBytes 312 } 313 314 data, err := unmarshalAs(bs, q.exemplar) 315 if err != nil { 316 log.Error("%s: %s Failed to unmarshal with error: %v", q.typ, q.name, err) 317 return errUnmarshal 318 } 319 320 log.Trace("%s %s: Task found: %#v", q.typ, q.name, data) 321 q.WorkerPool.Push(data) 322 return nil 323 } 324 325 // Shutdown processing from this queue 326 func (q *ByteFIFOQueue) Shutdown() { 327 log.Trace("%s: %s Shutting down", q.typ, q.name) 328 select { 329 case <-q.shutdownCtx.Done(): 330 return 331 default: 332 } 333 q.shutdownCtxCancel() 334 log.Debug("%s: %s Shutdown", q.typ, q.name) 335 } 336 337 // IsShutdown returns a channel which is closed when this Queue is shutdown 338 func (q *ByteFIFOQueue) IsShutdown() <-chan struct{} { 339 return q.shutdownCtx.Done() 340 } 341 342 // Terminate this queue and close the queue 343 func (q *ByteFIFOQueue) Terminate() { 344 log.Trace("%s: %s Terminating", q.typ, q.name) 345 q.Shutdown() 346 select { 347 case <-q.terminateCtx.Done(): 348 return 349 default: 350 } 351 if log.IsDebug() { 352 log.Debug("%s: %s Closing with %d tasks left in queue", q.typ, q.name, q.byteFIFO.Len(q.terminateCtx)) 353 } 354 q.terminateCtxCancel() 355 if err := q.byteFIFO.Close(); err != nil { 356 log.Error("Error whilst closing internal byte fifo in %s: %s: %v", q.typ, q.name, err) 357 } 358 q.baseCtxFinished() 359 log.Debug("%s: %s Terminated", q.typ, q.name) 360 } 361 362 // IsTerminated returns a channel which is closed when this Queue is terminated 363 func (q *ByteFIFOQueue) IsTerminated() <-chan struct{} { 364 return q.terminateCtx.Done() 365 } 366 367 var _ UniqueQueue = &ByteFIFOUniqueQueue{} 368 369 // ByteFIFOUniqueQueue represents a UniqueQueue formed from a UniqueByteFifo 370 type ByteFIFOUniqueQueue struct { 371 ByteFIFOQueue 372 } 373 374 // NewByteFIFOUniqueQueue creates a new ByteFIFOUniqueQueue 375 func NewByteFIFOUniqueQueue(typ Type, byteFIFO UniqueByteFIFO, handle HandlerFunc, cfg, exemplar interface{}) (*ByteFIFOUniqueQueue, error) { 376 configInterface, err := toConfig(ByteFIFOQueueConfiguration{}, cfg) 377 if err != nil { 378 return nil, err 379 } 380 config := configInterface.(ByteFIFOQueueConfiguration) 381 terminateCtx, terminateCtxCancel := context.WithCancel(context.Background()) 382 shutdownCtx, shutdownCtxCancel := context.WithCancel(terminateCtx) 383 384 q := &ByteFIFOUniqueQueue{ 385 ByteFIFOQueue: ByteFIFOQueue{ 386 byteFIFO: byteFIFO, 387 typ: typ, 388 shutdownCtx: shutdownCtx, 389 shutdownCtxCancel: shutdownCtxCancel, 390 terminateCtx: terminateCtx, 391 terminateCtxCancel: terminateCtxCancel, 392 exemplar: exemplar, 393 workers: config.Workers, 394 name: config.Name, 395 }, 396 } 397 q.WorkerPool = NewWorkerPool(func(data ...Data) (failed []Data) { 398 for _, unhandled := range handle(data...) { 399 if fail := q.PushBack(unhandled); fail != nil { 400 failed = append(failed, fail) 401 } 402 } 403 return failed 404 }, config.WorkerPoolConfiguration) 405 406 return q, nil 407 } 408 409 // Has checks if the provided data is in the queue 410 func (q *ByteFIFOUniqueQueue) Has(data Data) (bool, error) { 411 if !assignableTo(data, q.exemplar) { 412 return false, fmt.Errorf("unable to assign data: %v to same type as exemplar: %v in %s", data, q.exemplar, q.name) 413 } 414 bs, err := json.Marshal(data) 415 if err != nil { 416 return false, err 417 } 418 return q.byteFIFO.(UniqueByteFIFO).Has(q.terminateCtx, bs) 419 }