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