github.com/gitbundle/modules@v0.0.0-20231025071548-85b91c5c3b01/queue/manager.go (about) 1 // Copyright 2023 The GitBundle Inc. All rights reserved. 2 // Copyright 2017 The Gitea Authors. All rights reserved. 3 // Use of this source code is governed by a MIT-style 4 // license that can be found in the LICENSE file. 5 6 package queue 7 8 import ( 9 "context" 10 "fmt" 11 "reflect" 12 "sort" 13 "strings" 14 "sync" 15 "time" 16 17 "github.com/gitbundle/modules/json" 18 "github.com/gitbundle/modules/log" 19 ) 20 21 var manager *Manager 22 23 // Manager is a queue manager 24 type Manager struct { 25 mutex sync.Mutex 26 27 counter int64 28 Queues map[int64]*ManagedQueue 29 } 30 31 // ManagedQueue represents a working queue with a Pool of workers. 32 // 33 // Although a ManagedQueue should really represent a Queue this does not 34 // necessarily have to be the case. This could be used to describe any queue.WorkerPool. 35 type ManagedQueue struct { 36 mutex sync.Mutex 37 QID int64 38 Type Type 39 Name string 40 Configuration interface{} 41 ExemplarType string 42 Managed interface{} 43 counter int64 44 PoolWorkers map[int64]*PoolWorkers 45 } 46 47 // Flushable represents a pool or queue that is flushable 48 type Flushable interface { 49 // Flush will add a flush worker to the pool - the worker should be autoregistered with the manager 50 Flush(time.Duration) error 51 // FlushWithContext is very similar to Flush 52 // NB: The worker will not be registered with the manager. 53 FlushWithContext(ctx context.Context) error 54 // IsEmpty will return if the managed pool is empty and has no work 55 IsEmpty() bool 56 } 57 58 // Pausable represents a pool or queue that is Pausable 59 type Pausable interface { 60 // IsPaused will return if the pool or queue is paused 61 IsPaused() bool 62 // Pause will pause the pool or queue 63 Pause() 64 // Resume will resume the pool or queue 65 Resume() 66 // IsPausedIsResumed will return a bool indicating if the pool or queue is paused and a channel that will be closed when it is resumed 67 IsPausedIsResumed() (paused, resumed <-chan struct{}) 68 } 69 70 // ManagedPool is a simple interface to get certain details from a worker pool 71 type ManagedPool interface { 72 // AddWorkers adds a number of worker as group to the pool with the provided timeout. A CancelFunc is provided to cancel the group 73 AddWorkers(number int, timeout time.Duration) context.CancelFunc 74 // NumberOfWorkers returns the total number of workers in the pool 75 NumberOfWorkers() int 76 // MaxNumberOfWorkers returns the maximum number of workers the pool can dynamically grow to 77 MaxNumberOfWorkers() int 78 // SetMaxNumberOfWorkers sets the maximum number of workers the pool can dynamically grow to 79 SetMaxNumberOfWorkers(int) 80 // BoostTimeout returns the current timeout for worker groups created during a boost 81 BoostTimeout() time.Duration 82 // BlockTimeout returns the timeout the internal channel can block for before a boost would occur 83 BlockTimeout() time.Duration 84 // BoostWorkers sets the number of workers to be created during a boost 85 BoostWorkers() int 86 // SetPoolSettings sets the user updatable settings for the pool 87 SetPoolSettings(maxNumberOfWorkers, boostWorkers int, timeout time.Duration) 88 // NumberInQueue returns the total number of items in the pool 89 NumberInQueue() int64 90 // Done returns a channel that will be closed when the Pool's baseCtx is closed 91 Done() <-chan struct{} 92 } 93 94 // ManagedQueueList implements the sort.Interface 95 type ManagedQueueList []*ManagedQueue 96 97 // PoolWorkers represents a group of workers working on a queue 98 type PoolWorkers struct { 99 PID int64 100 Workers int 101 Start time.Time 102 Timeout time.Time 103 HasTimeout bool 104 Cancel context.CancelFunc 105 IsFlusher bool 106 } 107 108 // PoolWorkersList implements the sort.Interface for PoolWorkers 109 type PoolWorkersList []*PoolWorkers 110 111 func init() { 112 _ = GetManager() 113 } 114 115 // GetManager returns a Manager and initializes one as singleton if there's none yet 116 func GetManager() *Manager { 117 if manager == nil { 118 manager = &Manager{ 119 Queues: make(map[int64]*ManagedQueue), 120 } 121 } 122 return manager 123 } 124 125 // Add adds a queue to this manager 126 func (m *Manager) Add(managed interface{}, 127 t Type, 128 configuration, 129 exemplar interface{}, 130 ) int64 { 131 cfg, _ := json.Marshal(configuration) 132 mq := &ManagedQueue{ 133 Type: t, 134 Configuration: string(cfg), 135 ExemplarType: reflect.TypeOf(exemplar).String(), 136 PoolWorkers: make(map[int64]*PoolWorkers), 137 Managed: managed, 138 } 139 m.mutex.Lock() 140 m.counter++ 141 mq.QID = m.counter 142 mq.Name = fmt.Sprintf("queue-%d", mq.QID) 143 if named, ok := managed.(Named); ok { 144 name := named.Name() 145 if len(name) > 0 { 146 mq.Name = name 147 } 148 } 149 m.Queues[mq.QID] = mq 150 m.mutex.Unlock() 151 log.Trace("Queue Manager registered: %s (QID: %d)", mq.Name, mq.QID) 152 return mq.QID 153 } 154 155 // Remove a queue from the Manager 156 func (m *Manager) Remove(qid int64) { 157 m.mutex.Lock() 158 delete(m.Queues, qid) 159 m.mutex.Unlock() 160 log.Trace("Queue Manager removed: QID: %d", qid) 161 } 162 163 // GetManagedQueue by qid 164 func (m *Manager) GetManagedQueue(qid int64) *ManagedQueue { 165 m.mutex.Lock() 166 defer m.mutex.Unlock() 167 return m.Queues[qid] 168 } 169 170 // FlushAll flushes all the flushable queues attached to this manager 171 func (m *Manager) FlushAll(baseCtx context.Context, timeout time.Duration) error { 172 var ctx context.Context 173 var cancel context.CancelFunc 174 start := time.Now() 175 end := start 176 hasTimeout := false 177 if timeout > 0 { 178 ctx, cancel = context.WithTimeout(baseCtx, timeout) 179 end = start.Add(timeout) 180 hasTimeout = true 181 } else { 182 ctx, cancel = context.WithCancel(baseCtx) 183 } 184 defer cancel() 185 186 for { 187 select { 188 case <-ctx.Done(): 189 mqs := m.ManagedQueues() 190 nonEmptyQueues := []string{} 191 for _, mq := range mqs { 192 if !mq.IsEmpty() { 193 nonEmptyQueues = append(nonEmptyQueues, mq.Name) 194 } 195 } 196 if len(nonEmptyQueues) > 0 { 197 return fmt.Errorf("flush timeout with non-empty queues: %s", strings.Join(nonEmptyQueues, ", ")) 198 } 199 return nil 200 default: 201 } 202 mqs := m.ManagedQueues() 203 log.Debug("Found %d Managed Queues", len(mqs)) 204 wg := sync.WaitGroup{} 205 wg.Add(len(mqs)) 206 allEmpty := true 207 for _, mq := range mqs { 208 if mq.IsEmpty() { 209 wg.Done() 210 continue 211 } 212 if pausable, ok := mq.Managed.(Pausable); ok { 213 // no point flushing paused queues 214 if pausable.IsPaused() { 215 wg.Done() 216 continue 217 } 218 } 219 if pool, ok := mq.Managed.(ManagedPool); ok { 220 // No point into flushing pools when their base's ctx is already done. 221 select { 222 case <-pool.Done(): 223 wg.Done() 224 continue 225 default: 226 } 227 } 228 229 allEmpty = false 230 if flushable, ok := mq.Managed.(Flushable); ok { 231 log.Debug("Flushing (flushable) queue: %s", mq.Name) 232 go func(q *ManagedQueue) { 233 localCtx, localCtxCancel := context.WithCancel(ctx) 234 pid := q.RegisterWorkers(1, start, hasTimeout, end, localCtxCancel, true) 235 err := flushable.FlushWithContext(localCtx) 236 if err != nil && err != ctx.Err() { 237 cancel() 238 } 239 q.CancelWorkers(pid) 240 localCtxCancel() 241 wg.Done() 242 }(mq) 243 } else { 244 log.Debug("Queue: %s is non-empty but is not flushable", mq.Name) 245 wg.Done() 246 } 247 } 248 if allEmpty { 249 log.Debug("All queues are empty") 250 break 251 } 252 // Ensure there are always at least 100ms between loops but not more if we've actually been doing some flushing 253 // but don't delay cancellation here. 254 select { 255 case <-ctx.Done(): 256 case <-time.After(100 * time.Millisecond): 257 } 258 wg.Wait() 259 } 260 return nil 261 } 262 263 // ManagedQueues returns the managed queues 264 func (m *Manager) ManagedQueues() []*ManagedQueue { 265 m.mutex.Lock() 266 mqs := make([]*ManagedQueue, 0, len(m.Queues)) 267 for _, mq := range m.Queues { 268 mqs = append(mqs, mq) 269 } 270 m.mutex.Unlock() 271 sort.Sort(ManagedQueueList(mqs)) 272 return mqs 273 } 274 275 // Workers returns the poolworkers 276 func (q *ManagedQueue) Workers() []*PoolWorkers { 277 q.mutex.Lock() 278 workers := make([]*PoolWorkers, 0, len(q.PoolWorkers)) 279 for _, worker := range q.PoolWorkers { 280 workers = append(workers, worker) 281 } 282 q.mutex.Unlock() 283 sort.Sort(PoolWorkersList(workers)) 284 return workers 285 } 286 287 // RegisterWorkers registers workers to this queue 288 func (q *ManagedQueue) RegisterWorkers(number int, start time.Time, hasTimeout bool, timeout time.Time, cancel context.CancelFunc, isFlusher bool) int64 { 289 q.mutex.Lock() 290 defer q.mutex.Unlock() 291 q.counter++ 292 q.PoolWorkers[q.counter] = &PoolWorkers{ 293 PID: q.counter, 294 Workers: number, 295 Start: start, 296 Timeout: timeout, 297 HasTimeout: hasTimeout, 298 Cancel: cancel, 299 IsFlusher: isFlusher, 300 } 301 return q.counter 302 } 303 304 // CancelWorkers cancels pooled workers with pid 305 func (q *ManagedQueue) CancelWorkers(pid int64) { 306 q.mutex.Lock() 307 pw, ok := q.PoolWorkers[pid] 308 q.mutex.Unlock() 309 if !ok { 310 return 311 } 312 pw.Cancel() 313 } 314 315 // RemoveWorkers deletes pooled workers with pid 316 func (q *ManagedQueue) RemoveWorkers(pid int64) { 317 q.mutex.Lock() 318 pw, ok := q.PoolWorkers[pid] 319 delete(q.PoolWorkers, pid) 320 q.mutex.Unlock() 321 if ok && pw.Cancel != nil { 322 pw.Cancel() 323 } 324 } 325 326 // AddWorkers adds workers to the queue if it has registered an add worker function 327 func (q *ManagedQueue) AddWorkers(number int, timeout time.Duration) context.CancelFunc { 328 if pool, ok := q.Managed.(ManagedPool); ok { 329 // the cancel will be added to the pool workers description above 330 return pool.AddWorkers(number, timeout) 331 } 332 return nil 333 } 334 335 // Flushable returns true if the queue is flushable 336 func (q *ManagedQueue) Flushable() bool { 337 _, ok := q.Managed.(Flushable) 338 return ok 339 } 340 341 // Flush flushes the queue with a timeout 342 func (q *ManagedQueue) Flush(timeout time.Duration) error { 343 if flushable, ok := q.Managed.(Flushable); ok { 344 // the cancel will be added to the pool workers description above 345 return flushable.Flush(timeout) 346 } 347 return nil 348 } 349 350 // IsEmpty returns if the queue is empty 351 func (q *ManagedQueue) IsEmpty() bool { 352 if flushable, ok := q.Managed.(Flushable); ok { 353 return flushable.IsEmpty() 354 } 355 return true 356 } 357 358 // Pausable returns whether the queue is Pausable 359 func (q *ManagedQueue) Pausable() bool { 360 _, ok := q.Managed.(Pausable) 361 return ok 362 } 363 364 // Pause pauses the queue 365 func (q *ManagedQueue) Pause() { 366 if pausable, ok := q.Managed.(Pausable); ok { 367 pausable.Pause() 368 } 369 } 370 371 // IsPaused reveals if the queue is paused 372 func (q *ManagedQueue) IsPaused() bool { 373 if pausable, ok := q.Managed.(Pausable); ok { 374 return pausable.IsPaused() 375 } 376 return false 377 } 378 379 // Resume resumes the queue 380 func (q *ManagedQueue) Resume() { 381 if pausable, ok := q.Managed.(Pausable); ok { 382 pausable.Resume() 383 } 384 } 385 386 // NumberOfWorkers returns the number of workers in the queue 387 func (q *ManagedQueue) NumberOfWorkers() int { 388 if pool, ok := q.Managed.(ManagedPool); ok { 389 return pool.NumberOfWorkers() 390 } 391 return -1 392 } 393 394 // MaxNumberOfWorkers returns the maximum number of workers for the pool 395 func (q *ManagedQueue) MaxNumberOfWorkers() int { 396 if pool, ok := q.Managed.(ManagedPool); ok { 397 return pool.MaxNumberOfWorkers() 398 } 399 return 0 400 } 401 402 // BoostWorkers returns the number of workers for a boost 403 func (q *ManagedQueue) BoostWorkers() int { 404 if pool, ok := q.Managed.(ManagedPool); ok { 405 return pool.BoostWorkers() 406 } 407 return -1 408 } 409 410 // BoostTimeout returns the timeout of the next boost 411 func (q *ManagedQueue) BoostTimeout() time.Duration { 412 if pool, ok := q.Managed.(ManagedPool); ok { 413 return pool.BoostTimeout() 414 } 415 return 0 416 } 417 418 // BlockTimeout returns the timeout til the next boost 419 func (q *ManagedQueue) BlockTimeout() time.Duration { 420 if pool, ok := q.Managed.(ManagedPool); ok { 421 return pool.BlockTimeout() 422 } 423 return 0 424 } 425 426 // SetPoolSettings sets the setable boost values 427 func (q *ManagedQueue) SetPoolSettings(maxNumberOfWorkers, boostWorkers int, timeout time.Duration) { 428 if pool, ok := q.Managed.(ManagedPool); ok { 429 pool.SetPoolSettings(maxNumberOfWorkers, boostWorkers, timeout) 430 } 431 } 432 433 // NumberInQueue returns the number of items in the queue 434 func (q *ManagedQueue) NumberInQueue() int64 { 435 if pool, ok := q.Managed.(ManagedPool); ok { 436 return pool.NumberInQueue() 437 } 438 return -1 439 } 440 441 func (l ManagedQueueList) Len() int { 442 return len(l) 443 } 444 445 func (l ManagedQueueList) Less(i, j int) bool { 446 return l[i].Name < l[j].Name 447 } 448 449 func (l ManagedQueueList) Swap(i, j int) { 450 l[i], l[j] = l[j], l[i] 451 } 452 453 func (l PoolWorkersList) Len() int { 454 return len(l) 455 } 456 457 func (l PoolWorkersList) Less(i, j int) bool { 458 return l[i].Start.Before(l[j].Start) 459 } 460 461 func (l PoolWorkersList) Swap(i, j int) { 462 l[i], l[j] = l[j], l[i] 463 }