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  }