2020-01-07 11:23:09 +00:00
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package queue
import (
"context"
"fmt"
"reflect"
"sort"
2021-08-30 05:27:51 +01:00
"strings"
2020-01-07 11:23:09 +00:00
"sync"
"time"
2021-07-25 00:03:58 +08:00
"code.gitea.io/gitea/modules/json"
2020-01-07 11:23:09 +00:00
"code.gitea.io/gitea/modules/log"
)
var manager * Manager
// Manager is a queue manager
type Manager struct {
mutex sync . Mutex
counter int64
Queues map [ int64 ] * ManagedQueue
}
2020-01-29 01:01:06 +00:00
// ManagedQueue represents a working queue with a Pool of workers.
//
// Although a ManagedQueue should really represent a Queue this does not
// necessarily have to be the case. This could be used to describe any queue.WorkerPool.
2020-01-07 11:23:09 +00:00
type ManagedQueue struct {
mutex sync . Mutex
QID int64
Type Type
Name string
Configuration interface { }
ExemplarType string
2020-01-29 01:01:06 +00:00
Managed interface { }
2020-01-07 11:23:09 +00:00
counter int64
PoolWorkers map [ int64 ] * PoolWorkers
}
2020-01-29 01:01:06 +00:00
// Flushable represents a pool or queue that is flushable
type Flushable interface {
// Flush will add a flush worker to the pool - the worker should be autoregistered with the manager
Flush ( time . Duration ) error
// FlushWithContext is very similar to Flush
// NB: The worker will not be registered with the manager.
FlushWithContext ( ctx context . Context ) error
// IsEmpty will return if the managed pool is empty and has no work
IsEmpty ( ) bool
}
2022-01-22 21:22:14 +00:00
// Pausable represents a pool or queue that is Pausable
type Pausable interface {
// IsPaused will return if the pool or queue is paused
IsPaused ( ) bool
// Pause will pause the pool or queue
Pause ( )
// Resume will resume the pool or queue
Resume ( )
// IsPausedIsResumed will return a bool indicating if the pool or queue is paused and a channel that will be closed when it is resumed
IsPausedIsResumed ( ) ( paused , resumed <- chan struct { } )
}
2020-01-07 11:23:09 +00:00
// ManagedPool is a simple interface to get certain details from a worker pool
type ManagedPool interface {
2020-01-29 01:01:06 +00:00
// AddWorkers adds a number of worker as group to the pool with the provided timeout. A CancelFunc is provided to cancel the group
2020-01-07 11:23:09 +00:00
AddWorkers ( number int , timeout time . Duration ) context . CancelFunc
2020-01-29 01:01:06 +00:00
// NumberOfWorkers returns the total number of workers in the pool
2020-01-07 11:23:09 +00:00
NumberOfWorkers ( ) int
2020-01-29 01:01:06 +00:00
// MaxNumberOfWorkers returns the maximum number of workers the pool can dynamically grow to
2020-01-07 11:23:09 +00:00
MaxNumberOfWorkers ( ) int
2020-01-29 01:01:06 +00:00
// SetMaxNumberOfWorkers sets the maximum number of workers the pool can dynamically grow to
2020-01-07 11:23:09 +00:00
SetMaxNumberOfWorkers ( int )
2020-01-29 01:01:06 +00:00
// BoostTimeout returns the current timeout for worker groups created during a boost
2020-01-07 11:23:09 +00:00
BoostTimeout ( ) time . Duration
2020-01-29 01:01:06 +00:00
// BlockTimeout returns the timeout the internal channel can block for before a boost would occur
2020-01-07 11:23:09 +00:00
BlockTimeout ( ) time . Duration
2020-01-29 01:01:06 +00:00
// BoostWorkers sets the number of workers to be created during a boost
2020-01-07 11:23:09 +00:00
BoostWorkers ( ) int
2020-01-29 01:01:06 +00:00
// SetPoolSettings sets the user updatable settings for the pool
SetPoolSettings ( maxNumberOfWorkers , boostWorkers int , timeout time . Duration )
2022-02-05 20:51:25 +00:00
// Done returns a channel that will be closed when the Pool's baseCtx is closed
Done ( ) <- chan struct { }
2020-01-07 11:23:09 +00:00
}
// ManagedQueueList implements the sort.Interface
type ManagedQueueList [ ] * ManagedQueue
2020-01-29 01:01:06 +00:00
// PoolWorkers represents a group of workers working on a queue
2020-01-07 11:23:09 +00:00
type PoolWorkers struct {
PID int64
Workers int
Start time . Time
Timeout time . Time
HasTimeout bool
Cancel context . CancelFunc
2020-01-29 01:01:06 +00:00
IsFlusher bool
2020-01-07 11:23:09 +00:00
}
2020-01-29 01:01:06 +00:00
// PoolWorkersList implements the sort.Interface for PoolWorkers
2020-01-07 11:23:09 +00:00
type PoolWorkersList [ ] * PoolWorkers
func init ( ) {
_ = GetManager ( )
}
// GetManager returns a Manager and initializes one as singleton if there's none yet
func GetManager ( ) * Manager {
if manager == nil {
manager = & Manager {
Queues : make ( map [ int64 ] * ManagedQueue ) ,
}
}
return manager
}
// Add adds a queue to this manager
2020-01-29 01:01:06 +00:00
func ( m * Manager ) Add ( managed interface { } ,
2020-01-07 11:23:09 +00:00
t Type ,
configuration ,
2022-01-20 18:46:10 +01:00
exemplar interface { } ,
) int64 {
2020-01-07 11:23:09 +00:00
cfg , _ := json . Marshal ( configuration )
mq := & ManagedQueue {
Type : t ,
Configuration : string ( cfg ) ,
ExemplarType : reflect . TypeOf ( exemplar ) . String ( ) ,
PoolWorkers : make ( map [ int64 ] * PoolWorkers ) ,
2020-01-29 01:01:06 +00:00
Managed : managed ,
2020-01-07 11:23:09 +00:00
}
m . mutex . Lock ( )
m . counter ++
mq . QID = m . counter
mq . Name = fmt . Sprintf ( "queue-%d" , mq . QID )
2020-01-29 01:01:06 +00:00
if named , ok := managed . ( Named ) ; ok {
name := named . Name ( )
if len ( name ) > 0 {
mq . Name = name
}
2020-01-07 11:23:09 +00:00
}
m . Queues [ mq . QID ] = mq
m . mutex . Unlock ( )
log . Trace ( "Queue Manager registered: %s (QID: %d)" , mq . Name , mq . QID )
return mq . QID
}
// Remove a queue from the Manager
func ( m * Manager ) Remove ( qid int64 ) {
m . mutex . Lock ( )
delete ( m . Queues , qid )
m . mutex . Unlock ( )
log . Trace ( "Queue Manager removed: QID: %d" , qid )
}
// GetManagedQueue by qid
func ( m * Manager ) GetManagedQueue ( qid int64 ) * ManagedQueue {
m . mutex . Lock ( )
defer m . mutex . Unlock ( )
return m . Queues [ qid ]
}
2020-01-29 01:01:06 +00:00
// FlushAll flushes all the flushable queues attached to this manager
func ( m * Manager ) FlushAll ( baseCtx context . Context , timeout time . Duration ) error {
var ctx context . Context
var cancel context . CancelFunc
start := time . Now ( )
end := start
hasTimeout := false
if timeout > 0 {
ctx , cancel = context . WithTimeout ( baseCtx , timeout )
end = start . Add ( timeout )
hasTimeout = true
} else {
ctx , cancel = context . WithCancel ( baseCtx )
}
defer cancel ( )
for {
select {
case <- ctx . Done ( ) :
2021-08-30 05:27:51 +01:00
mqs := m . ManagedQueues ( )
nonEmptyQueues := [ ] string { }
for _ , mq := range mqs {
if ! mq . IsEmpty ( ) {
nonEmptyQueues = append ( nonEmptyQueues , mq . Name )
}
}
if len ( nonEmptyQueues ) > 0 {
return fmt . Errorf ( "flush timeout with non-empty queues: %s" , strings . Join ( nonEmptyQueues , ", " ) )
}
return nil
2020-01-29 01:01:06 +00:00
default :
}
mqs := m . ManagedQueues ( )
2021-04-10 09:27:29 +01:00
log . Debug ( "Found %d Managed Queues" , len ( mqs ) )
2020-01-29 01:01:06 +00:00
wg := sync . WaitGroup { }
wg . Add ( len ( mqs ) )
allEmpty := true
for _ , mq := range mqs {
if mq . IsEmpty ( ) {
wg . Done ( )
continue
}
2022-01-22 21:22:14 +00:00
if pausable , ok := mq . Managed . ( Pausable ) ; ok {
// no point flushing paused queues
if pausable . IsPaused ( ) {
wg . Done ( )
continue
}
}
2022-02-05 20:51:25 +00:00
if pool , ok := mq . Managed . ( ManagedPool ) ; ok {
// No point into flushing pools when their base's ctx is already done.
select {
case <- pool . Done ( ) :
wg . Done ( )
continue
default :
}
}
2022-01-22 21:22:14 +00:00
2020-01-29 01:01:06 +00:00
allEmpty = false
if flushable , ok := mq . Managed . ( Flushable ) ; ok {
2021-04-10 09:27:29 +01:00
log . Debug ( "Flushing (flushable) queue: %s" , mq . Name )
2020-02-01 23:43:50 +00:00
go func ( q * ManagedQueue ) {
2021-05-15 15:22:26 +01:00
localCtx , localCtxCancel := context . WithCancel ( ctx )
pid := q . RegisterWorkers ( 1 , start , hasTimeout , end , localCtxCancel , true )
2020-01-29 01:01:06 +00:00
err := flushable . FlushWithContext ( localCtx )
if err != nil && err != ctx . Err ( ) {
cancel ( )
}
2020-02-01 23:43:50 +00:00
q . CancelWorkers ( pid )
2021-05-15 15:22:26 +01:00
localCtxCancel ( )
2020-01-29 01:01:06 +00:00
wg . Done ( )
2020-02-01 23:43:50 +00:00
} ( mq )
2020-01-29 01:01:06 +00:00
} else {
2021-05-12 00:22:08 +01:00
log . Debug ( "Queue: %s is non-empty but is not flushable" , mq . Name )
wg . Done ( )
2020-01-29 01:01:06 +00:00
}
}
if allEmpty {
2021-05-12 00:22:08 +01:00
log . Debug ( "All queues are empty" )
2020-01-29 01:01:06 +00:00
break
}
2022-01-22 21:22:14 +00:00
// Ensure there are always at least 100ms between loops but not more if we've actually been doing some flushing
2021-05-12 00:22:08 +01:00
// but don't delay cancellation here.
select {
case <- ctx . Done ( ) :
case <- time . After ( 100 * time . Millisecond ) :
}
2020-01-29 01:01:06 +00:00
wg . Wait ( )
}
return nil
}
2020-01-07 11:23:09 +00:00
// ManagedQueues returns the managed queues
func ( m * Manager ) ManagedQueues ( ) [ ] * ManagedQueue {
m . mutex . Lock ( )
mqs := make ( [ ] * ManagedQueue , 0 , len ( m . Queues ) )
for _ , mq := range m . Queues {
mqs = append ( mqs , mq )
}
m . mutex . Unlock ( )
sort . Sort ( ManagedQueueList ( mqs ) )
return mqs
}
// Workers returns the poolworkers
func ( q * ManagedQueue ) Workers ( ) [ ] * PoolWorkers {
q . mutex . Lock ( )
workers := make ( [ ] * PoolWorkers , 0 , len ( q . PoolWorkers ) )
for _ , worker := range q . PoolWorkers {
workers = append ( workers , worker )
}
q . mutex . Unlock ( )
sort . Sort ( PoolWorkersList ( workers ) )
return workers
}
// RegisterWorkers registers workers to this queue
2020-01-29 01:01:06 +00:00
func ( q * ManagedQueue ) RegisterWorkers ( number int , start time . Time , hasTimeout bool , timeout time . Time , cancel context . CancelFunc , isFlusher bool ) int64 {
2020-01-07 11:23:09 +00:00
q . mutex . Lock ( )
defer q . mutex . Unlock ( )
q . counter ++
q . PoolWorkers [ q . counter ] = & PoolWorkers {
PID : q . counter ,
Workers : number ,
Start : start ,
Timeout : timeout ,
HasTimeout : hasTimeout ,
Cancel : cancel ,
2020-01-29 01:01:06 +00:00
IsFlusher : isFlusher ,
2020-01-07 11:23:09 +00:00
}
return q . counter
}
// CancelWorkers cancels pooled workers with pid
func ( q * ManagedQueue ) CancelWorkers ( pid int64 ) {
q . mutex . Lock ( )
pw , ok := q . PoolWorkers [ pid ]
q . mutex . Unlock ( )
if ! ok {
return
}
pw . Cancel ( )
}
// RemoveWorkers deletes pooled workers with pid
func ( q * ManagedQueue ) RemoveWorkers ( pid int64 ) {
q . mutex . Lock ( )
pw , ok := q . PoolWorkers [ pid ]
delete ( q . PoolWorkers , pid )
q . mutex . Unlock ( )
if ok && pw . Cancel != nil {
pw . Cancel ( )
}
}
// AddWorkers adds workers to the queue if it has registered an add worker function
func ( q * ManagedQueue ) AddWorkers ( number int , timeout time . Duration ) context . CancelFunc {
2020-01-29 01:01:06 +00:00
if pool , ok := q . Managed . ( ManagedPool ) ; ok {
2020-01-07 11:23:09 +00:00
// the cancel will be added to the pool workers description above
2020-01-29 01:01:06 +00:00
return pool . AddWorkers ( number , timeout )
2020-01-07 11:23:09 +00:00
}
return nil
}
2022-01-22 21:22:14 +00:00
// Flushable returns true if the queue is flushable
func ( q * ManagedQueue ) Flushable ( ) bool {
_ , ok := q . Managed . ( Flushable )
return ok
}
2020-01-29 01:01:06 +00:00
// Flush flushes the queue with a timeout
func ( q * ManagedQueue ) Flush ( timeout time . Duration ) error {
if flushable , ok := q . Managed . ( Flushable ) ; ok {
// the cancel will be added to the pool workers description above
return flushable . Flush ( timeout )
}
return nil
}
// IsEmpty returns if the queue is empty
func ( q * ManagedQueue ) IsEmpty ( ) bool {
if flushable , ok := q . Managed . ( Flushable ) ; ok {
return flushable . IsEmpty ( )
}
return true
}
2022-01-22 21:22:14 +00:00
// Pausable returns whether the queue is Pausable
func ( q * ManagedQueue ) Pausable ( ) bool {
_ , ok := q . Managed . ( Pausable )
return ok
}
// Pause pauses the queue
func ( q * ManagedQueue ) Pause ( ) {
if pausable , ok := q . Managed . ( Pausable ) ; ok {
pausable . Pause ( )
}
}
// IsPaused reveals if the queue is paused
func ( q * ManagedQueue ) IsPaused ( ) bool {
if pausable , ok := q . Managed . ( Pausable ) ; ok {
return pausable . IsPaused ( )
}
return false
}
// Resume resumes the queue
func ( q * ManagedQueue ) Resume ( ) {
if pausable , ok := q . Managed . ( Pausable ) ; ok {
pausable . Resume ( )
}
}
2020-01-07 11:23:09 +00:00
// NumberOfWorkers returns the number of workers in the queue
func ( q * ManagedQueue ) NumberOfWorkers ( ) int {
2020-01-29 01:01:06 +00:00
if pool , ok := q . Managed . ( ManagedPool ) ; ok {
return pool . NumberOfWorkers ( )
2020-01-07 11:23:09 +00:00
}
return - 1
}
// MaxNumberOfWorkers returns the maximum number of workers for the pool
func ( q * ManagedQueue ) MaxNumberOfWorkers ( ) int {
2020-01-29 01:01:06 +00:00
if pool , ok := q . Managed . ( ManagedPool ) ; ok {
return pool . MaxNumberOfWorkers ( )
2020-01-07 11:23:09 +00:00
}
return 0
}
// BoostWorkers returns the number of workers for a boost
func ( q * ManagedQueue ) BoostWorkers ( ) int {
2020-01-29 01:01:06 +00:00
if pool , ok := q . Managed . ( ManagedPool ) ; ok {
return pool . BoostWorkers ( )
2020-01-07 11:23:09 +00:00
}
return - 1
}
// BoostTimeout returns the timeout of the next boost
func ( q * ManagedQueue ) BoostTimeout ( ) time . Duration {
2020-01-29 01:01:06 +00:00
if pool , ok := q . Managed . ( ManagedPool ) ; ok {
return pool . BoostTimeout ( )
2020-01-07 11:23:09 +00:00
}
return 0
}
// BlockTimeout returns the timeout til the next boost
func ( q * ManagedQueue ) BlockTimeout ( ) time . Duration {
2020-01-29 01:01:06 +00:00
if pool , ok := q . Managed . ( ManagedPool ) ; ok {
return pool . BlockTimeout ( )
2020-01-07 11:23:09 +00:00
}
return 0
}
2020-01-29 01:01:06 +00:00
// SetPoolSettings sets the setable boost values
func ( q * ManagedQueue ) SetPoolSettings ( maxNumberOfWorkers , boostWorkers int , timeout time . Duration ) {
if pool , ok := q . Managed . ( ManagedPool ) ; ok {
pool . SetPoolSettings ( maxNumberOfWorkers , boostWorkers , timeout )
2020-01-07 11:23:09 +00:00
}
}
func ( l ManagedQueueList ) Len ( ) int {
return len ( l )
}
func ( l ManagedQueueList ) Less ( i , j int ) bool {
return l [ i ] . Name < l [ j ] . Name
}
func ( l ManagedQueueList ) Swap ( i , j int ) {
l [ i ] , l [ j ] = l [ j ] , l [ i ]
}
func ( l PoolWorkersList ) Len ( ) int {
return len ( l )
}
func ( l PoolWorkersList ) Less ( i , j int ) bool {
return l [ i ] . Start . Before ( l [ j ] . Start )
}
func ( l PoolWorkersList ) Swap ( i , j int ) {
l [ i ] , l [ j ] = l [ j ] , l [ i ]
}