code.gitea.io/gitea@v1.19.3/modules/queue/queue_wrapped.go (about)

     1  // Copyright 2019 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package queue
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"sync"
    10  	"sync/atomic"
    11  	"time"
    12  
    13  	"code.gitea.io/gitea/modules/log"
    14  	"code.gitea.io/gitea/modules/util"
    15  )
    16  
    17  // WrappedQueueType is the type for a wrapped delayed starting queue
    18  const WrappedQueueType Type = "wrapped"
    19  
    20  // WrappedQueueConfiguration is the configuration for a WrappedQueue
    21  type WrappedQueueConfiguration struct {
    22  	Underlying  Type
    23  	Timeout     time.Duration
    24  	MaxAttempts int
    25  	Config      interface{}
    26  	QueueLength int
    27  	Name        string
    28  }
    29  
    30  type delayedStarter struct {
    31  	internal    Queue
    32  	underlying  Type
    33  	cfg         interface{}
    34  	timeout     time.Duration
    35  	maxAttempts int
    36  	name        string
    37  }
    38  
    39  // setInternal must be called with the lock locked.
    40  func (q *delayedStarter) setInternal(atShutdown func(func()), handle HandlerFunc, exemplar interface{}) error {
    41  	var ctx context.Context
    42  	var cancel context.CancelFunc
    43  	if q.timeout > 0 {
    44  		ctx, cancel = context.WithTimeout(context.Background(), q.timeout)
    45  	} else {
    46  		ctx, cancel = context.WithCancel(context.Background())
    47  	}
    48  
    49  	defer cancel()
    50  	// Ensure we also stop at shutdown
    51  	atShutdown(cancel)
    52  
    53  	i := 1
    54  	for q.internal == nil {
    55  		select {
    56  		case <-ctx.Done():
    57  			cfg := q.cfg
    58  			if s, ok := cfg.([]byte); ok {
    59  				cfg = string(s)
    60  			}
    61  			return fmt.Errorf("timedout creating queue %v with cfg %#v in %s", q.underlying, cfg, q.name)
    62  		default:
    63  			queue, err := NewQueue(q.underlying, handle, q.cfg, exemplar)
    64  			if err == nil {
    65  				q.internal = queue
    66  				break
    67  			}
    68  			if err.Error() != "resource temporarily unavailable" {
    69  				if bs, ok := q.cfg.([]byte); ok {
    70  					log.Warn("[Attempt: %d] Failed to create queue: %v for %s cfg: %s error: %v", i, q.underlying, q.name, string(bs), err)
    71  				} else {
    72  					log.Warn("[Attempt: %d] Failed to create queue: %v for %s cfg: %#v error: %v", i, q.underlying, q.name, q.cfg, err)
    73  				}
    74  			}
    75  			i++
    76  			if q.maxAttempts > 0 && i > q.maxAttempts {
    77  				if bs, ok := q.cfg.([]byte); ok {
    78  					return fmt.Errorf("unable to create queue %v for %s with cfg %s by max attempts: error: %w", q.underlying, q.name, string(bs), err)
    79  				}
    80  				return fmt.Errorf("unable to create queue %v for %s with cfg %#v by max attempts: error: %w", q.underlying, q.name, q.cfg, err)
    81  			}
    82  			sleepTime := 100 * time.Millisecond
    83  			if q.timeout > 0 && q.maxAttempts > 0 {
    84  				sleepTime = (q.timeout - 200*time.Millisecond) / time.Duration(q.maxAttempts)
    85  			}
    86  			t := time.NewTimer(sleepTime)
    87  			select {
    88  			case <-ctx.Done():
    89  				util.StopTimer(t)
    90  			case <-t.C:
    91  			}
    92  		}
    93  	}
    94  	return nil
    95  }
    96  
    97  // WrappedQueue wraps a delayed starting queue
    98  type WrappedQueue struct {
    99  	delayedStarter
   100  	lock       sync.Mutex
   101  	handle     HandlerFunc
   102  	exemplar   interface{}
   103  	channel    chan Data
   104  	numInQueue int64
   105  }
   106  
   107  // NewWrappedQueue will attempt to create a queue of the provided type,
   108  // but if there is a problem creating this queue it will instead create
   109  // a WrappedQueue with delayed startup of the queue instead and a
   110  // channel which will be redirected to the queue
   111  func NewWrappedQueue(handle HandlerFunc, cfg, exemplar interface{}) (Queue, error) {
   112  	configInterface, err := toConfig(WrappedQueueConfiguration{}, cfg)
   113  	if err != nil {
   114  		return nil, err
   115  	}
   116  	config := configInterface.(WrappedQueueConfiguration)
   117  
   118  	queue, err := NewQueue(config.Underlying, handle, config.Config, exemplar)
   119  	if err == nil {
   120  		// Just return the queue there is no need to wrap
   121  		return queue, nil
   122  	}
   123  	if IsErrInvalidConfiguration(err) {
   124  		// Retrying ain't gonna make this any better...
   125  		return nil, ErrInvalidConfiguration{cfg: cfg}
   126  	}
   127  
   128  	queue = &WrappedQueue{
   129  		handle:   handle,
   130  		channel:  make(chan Data, config.QueueLength),
   131  		exemplar: exemplar,
   132  		delayedStarter: delayedStarter{
   133  			cfg:         config.Config,
   134  			underlying:  config.Underlying,
   135  			timeout:     config.Timeout,
   136  			maxAttempts: config.MaxAttempts,
   137  			name:        config.Name,
   138  		},
   139  	}
   140  	_ = GetManager().Add(queue, WrappedQueueType, config, exemplar)
   141  	return queue, nil
   142  }
   143  
   144  // Name returns the name of the queue
   145  func (q *WrappedQueue) Name() string {
   146  	return q.name + "-wrapper"
   147  }
   148  
   149  // Push will push the data to the internal channel checking it against the exemplar
   150  func (q *WrappedQueue) Push(data Data) error {
   151  	if !assignableTo(data, q.exemplar) {
   152  		return fmt.Errorf("unable to assign data: %v to same type as exemplar: %v in %s", data, q.exemplar, q.name)
   153  	}
   154  	atomic.AddInt64(&q.numInQueue, 1)
   155  	q.channel <- data
   156  	return nil
   157  }
   158  
   159  func (q *WrappedQueue) flushInternalWithContext(ctx context.Context) error {
   160  	q.lock.Lock()
   161  	if q.internal == nil {
   162  		q.lock.Unlock()
   163  		return fmt.Errorf("not ready to flush wrapped queue %s yet", q.Name())
   164  	}
   165  	q.lock.Unlock()
   166  	select {
   167  	case <-ctx.Done():
   168  		return ctx.Err()
   169  	default:
   170  	}
   171  	return q.internal.FlushWithContext(ctx)
   172  }
   173  
   174  // Flush flushes the queue and blocks till the queue is empty
   175  func (q *WrappedQueue) Flush(timeout time.Duration) error {
   176  	var ctx context.Context
   177  	var cancel context.CancelFunc
   178  	if timeout > 0 {
   179  		ctx, cancel = context.WithTimeout(context.Background(), timeout)
   180  	} else {
   181  		ctx, cancel = context.WithCancel(context.Background())
   182  	}
   183  	defer cancel()
   184  	return q.FlushWithContext(ctx)
   185  }
   186  
   187  // FlushWithContext implements the final part of Flushable
   188  func (q *WrappedQueue) FlushWithContext(ctx context.Context) error {
   189  	log.Trace("WrappedQueue: %s FlushWithContext", q.Name())
   190  	errChan := make(chan error, 1)
   191  	go func() {
   192  		errChan <- q.flushInternalWithContext(ctx)
   193  		close(errChan)
   194  	}()
   195  
   196  	select {
   197  	case err := <-errChan:
   198  		return err
   199  	case <-ctx.Done():
   200  		go func() {
   201  			<-errChan
   202  		}()
   203  		return ctx.Err()
   204  	}
   205  }
   206  
   207  // IsEmpty checks whether the queue is empty
   208  func (q *WrappedQueue) IsEmpty() bool {
   209  	if atomic.LoadInt64(&q.numInQueue) != 0 {
   210  		return false
   211  	}
   212  	q.lock.Lock()
   213  	defer q.lock.Unlock()
   214  	if q.internal == nil {
   215  		return false
   216  	}
   217  	return q.internal.IsEmpty()
   218  }
   219  
   220  // Run starts to run the queue and attempts to create the internal queue
   221  func (q *WrappedQueue) Run(atShutdown, atTerminate func(func())) {
   222  	log.Debug("WrappedQueue: %s Starting", q.name)
   223  	q.lock.Lock()
   224  	if q.internal == nil {
   225  		err := q.setInternal(atShutdown, q.handle, q.exemplar)
   226  		q.lock.Unlock()
   227  		if err != nil {
   228  			log.Fatal("Unable to set the internal queue for %s Error: %v", q.Name(), err)
   229  			return
   230  		}
   231  		go func() {
   232  			for data := range q.channel {
   233  				_ = q.internal.Push(data)
   234  				atomic.AddInt64(&q.numInQueue, -1)
   235  			}
   236  		}()
   237  	} else {
   238  		q.lock.Unlock()
   239  	}
   240  
   241  	q.internal.Run(atShutdown, atTerminate)
   242  	log.Trace("WrappedQueue: %s Done", q.name)
   243  }
   244  
   245  // Shutdown this queue and stop processing
   246  func (q *WrappedQueue) Shutdown() {
   247  	log.Trace("WrappedQueue: %s Shutting down", q.name)
   248  	q.lock.Lock()
   249  	defer q.lock.Unlock()
   250  	if q.internal == nil {
   251  		return
   252  	}
   253  	if shutdownable, ok := q.internal.(Shutdownable); ok {
   254  		shutdownable.Shutdown()
   255  	}
   256  	log.Debug("WrappedQueue: %s Shutdown", q.name)
   257  }
   258  
   259  // Terminate this queue and close the queue
   260  func (q *WrappedQueue) Terminate() {
   261  	log.Trace("WrappedQueue: %s Terminating", q.name)
   262  	q.lock.Lock()
   263  	defer q.lock.Unlock()
   264  	if q.internal == nil {
   265  		return
   266  	}
   267  	if shutdownable, ok := q.internal.(Shutdownable); ok {
   268  		shutdownable.Terminate()
   269  	}
   270  	log.Debug("WrappedQueue: %s Terminated", q.name)
   271  }
   272  
   273  // IsPaused will return if the pool or queue is paused
   274  func (q *WrappedQueue) IsPaused() bool {
   275  	q.lock.Lock()
   276  	defer q.lock.Unlock()
   277  	pausable, ok := q.internal.(Pausable)
   278  	return ok && pausable.IsPaused()
   279  }
   280  
   281  // Pause will pause the pool or queue
   282  func (q *WrappedQueue) Pause() {
   283  	q.lock.Lock()
   284  	defer q.lock.Unlock()
   285  	if pausable, ok := q.internal.(Pausable); ok {
   286  		pausable.Pause()
   287  	}
   288  }
   289  
   290  // Resume will resume the pool or queue
   291  func (q *WrappedQueue) Resume() {
   292  	q.lock.Lock()
   293  	defer q.lock.Unlock()
   294  	if pausable, ok := q.internal.(Pausable); ok {
   295  		pausable.Resume()
   296  	}
   297  }
   298  
   299  // IsPausedIsResumed will return a bool indicating if the pool or queue is paused and a channel that will be closed when it is resumed
   300  func (q *WrappedQueue) IsPausedIsResumed() (paused, resumed <-chan struct{}) {
   301  	q.lock.Lock()
   302  	defer q.lock.Unlock()
   303  	if pausable, ok := q.internal.(Pausable); ok {
   304  		return pausable.IsPausedIsResumed()
   305  	}
   306  	return context.Background().Done(), closedChan
   307  }
   308  
   309  var closedChan chan struct{}
   310  
   311  func init() {
   312  	queuesMap[WrappedQueueType] = NewWrappedQueue
   313  	closedChan = make(chan struct{})
   314  	close(closedChan)
   315  }