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  }