github.com/gitbundle/modules@v0.0.0-20231025071548-85b91c5c3b01/queue/queue_disk_channel.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  	"runtime/pprof"
    12  	"sync"
    13  	"sync/atomic"
    14  	"time"
    15  
    16  	"github.com/gitbundle/modules/log"
    17  )
    18  
    19  // PersistableChannelQueueType is the type for persistable queue
    20  const PersistableChannelQueueType Type = "persistable-channel"
    21  
    22  // PersistableChannelQueueConfiguration is the configuration for a PersistableChannelQueue
    23  type PersistableChannelQueueConfiguration struct {
    24  	Name         string
    25  	DataDir      string
    26  	BatchLength  int
    27  	QueueLength  int
    28  	Timeout      time.Duration
    29  	MaxAttempts  int
    30  	Workers      int
    31  	MaxWorkers   int
    32  	BlockTimeout time.Duration
    33  	BoostTimeout time.Duration
    34  	BoostWorkers int
    35  }
    36  
    37  // PersistableChannelQueue wraps a channel queue and level queue together
    38  // The disk level queue will be used to store data at shutdown and terminate - and will be restored
    39  // on start up.
    40  type PersistableChannelQueue struct {
    41  	channelQueue *ChannelQueue
    42  	delayedStarter
    43  	lock   sync.Mutex
    44  	closed chan struct{}
    45  }
    46  
    47  // NewPersistableChannelQueue creates a wrapped batched channel queue with persistable level queue backend when shutting down
    48  // This differs from a wrapped queue in that the persistent queue is only used to persist at shutdown/terminate
    49  func NewPersistableChannelQueue(handle HandlerFunc, cfg, exemplar interface{}) (Queue, error) {
    50  	configInterface, err := toConfig(PersistableChannelQueueConfiguration{}, cfg)
    51  	if err != nil {
    52  		return nil, err
    53  	}
    54  	config := configInterface.(PersistableChannelQueueConfiguration)
    55  
    56  	queue := &PersistableChannelQueue{
    57  		closed: make(chan struct{}),
    58  	}
    59  
    60  	wrappedHandle := func(data ...Data) (failed []Data) {
    61  		for _, unhandled := range handle(data...) {
    62  			if fail := queue.PushBack(unhandled); fail != nil {
    63  				failed = append(failed, fail)
    64  			}
    65  		}
    66  		return
    67  	}
    68  
    69  	channelQueue, err := NewChannelQueue(wrappedHandle, ChannelQueueConfiguration{
    70  		WorkerPoolConfiguration: WorkerPoolConfiguration{
    71  			QueueLength:  config.QueueLength,
    72  			BatchLength:  config.BatchLength,
    73  			BlockTimeout: config.BlockTimeout,
    74  			BoostTimeout: config.BoostTimeout,
    75  			BoostWorkers: config.BoostWorkers,
    76  			MaxWorkers:   config.MaxWorkers,
    77  			Name:         config.Name + "-channel",
    78  		},
    79  		Workers: config.Workers,
    80  	}, exemplar)
    81  	if err != nil {
    82  		return nil, err
    83  	}
    84  
    85  	// the level backend only needs temporary workers to catch up with the previously dropped work
    86  	levelCfg := LevelQueueConfiguration{
    87  		ByteFIFOQueueConfiguration: ByteFIFOQueueConfiguration{
    88  			WorkerPoolConfiguration: WorkerPoolConfiguration{
    89  				QueueLength:  config.QueueLength,
    90  				BatchLength:  config.BatchLength,
    91  				BlockTimeout: 1 * time.Second,
    92  				BoostTimeout: 5 * time.Minute,
    93  				BoostWorkers: 1,
    94  				MaxWorkers:   5,
    95  				Name:         config.Name + "-level",
    96  			},
    97  			Workers: 0,
    98  		},
    99  		DataDir: config.DataDir,
   100  	}
   101  
   102  	levelQueue, err := NewLevelQueue(wrappedHandle, levelCfg, exemplar)
   103  	if err == nil {
   104  		queue.channelQueue = channelQueue.(*ChannelQueue)
   105  		queue.delayedStarter = delayedStarter{
   106  			internal: levelQueue.(*LevelQueue),
   107  			name:     config.Name,
   108  		}
   109  		_ = GetManager().Add(queue, PersistableChannelQueueType, config, exemplar)
   110  		return queue, nil
   111  	}
   112  	if IsErrInvalidConfiguration(err) {
   113  		// Retrying ain't gonna make this any better...
   114  		return nil, ErrInvalidConfiguration{cfg: cfg}
   115  	}
   116  
   117  	queue.channelQueue = channelQueue.(*ChannelQueue)
   118  	queue.delayedStarter = delayedStarter{
   119  		cfg:         levelCfg,
   120  		underlying:  LevelQueueType,
   121  		timeout:     config.Timeout,
   122  		maxAttempts: config.MaxAttempts,
   123  		name:        config.Name,
   124  	}
   125  	_ = GetManager().Add(queue, PersistableChannelQueueType, config, exemplar)
   126  	return queue, nil
   127  }
   128  
   129  // Name returns the name of this queue
   130  func (q *PersistableChannelQueue) Name() string {
   131  	return q.delayedStarter.name
   132  }
   133  
   134  // Push will push the indexer data to queue
   135  func (q *PersistableChannelQueue) Push(data Data) error {
   136  	select {
   137  	case <-q.closed:
   138  		return q.internal.Push(data)
   139  	default:
   140  		return q.channelQueue.Push(data)
   141  	}
   142  }
   143  
   144  // PushBack will push the indexer data to queue
   145  func (q *PersistableChannelQueue) PushBack(data Data) error {
   146  	select {
   147  	case <-q.closed:
   148  		if pbr, ok := q.internal.(PushBackable); ok {
   149  			return pbr.PushBack(data)
   150  		}
   151  		return q.internal.Push(data)
   152  	default:
   153  		return q.channelQueue.Push(data)
   154  	}
   155  }
   156  
   157  // Run starts to run the queue
   158  func (q *PersistableChannelQueue) Run(atShutdown, atTerminate func(func())) {
   159  	pprof.SetGoroutineLabels(q.channelQueue.baseCtx)
   160  	log.Debug("PersistableChannelQueue: %s Starting", q.delayedStarter.name)
   161  	_ = q.channelQueue.AddWorkers(q.channelQueue.workers, 0)
   162  
   163  	q.lock.Lock()
   164  	if q.internal == nil {
   165  		err := q.setInternal(atShutdown, q.channelQueue.handle, q.channelQueue.exemplar)
   166  		q.lock.Unlock()
   167  		if err != nil {
   168  			log.Fatal("Unable to create internal queue for %s Error: %v", q.Name(), err)
   169  			return
   170  		}
   171  	} else {
   172  		q.lock.Unlock()
   173  	}
   174  	atShutdown(q.Shutdown)
   175  	atTerminate(q.Terminate)
   176  
   177  	if lq, ok := q.internal.(*LevelQueue); ok && lq.byteFIFO.Len(lq.shutdownCtx) != 0 {
   178  		// Just run the level queue - we shut it down once it's flushed
   179  		go q.internal.Run(func(_ func()) {}, func(_ func()) {})
   180  		go func() {
   181  			for !q.IsEmpty() {
   182  				_ = q.internal.Flush(0)
   183  				select {
   184  				case <-time.After(100 * time.Millisecond):
   185  				case <-q.internal.(*LevelQueue).shutdownCtx.Done():
   186  					log.Warn("LevelQueue: %s shut down before completely flushed", q.internal.(*LevelQueue).Name())
   187  					return
   188  				}
   189  			}
   190  			log.Debug("LevelQueue: %s flushed so shutting down", q.internal.(*LevelQueue).Name())
   191  			q.internal.(*LevelQueue).Shutdown()
   192  			GetManager().Remove(q.internal.(*LevelQueue).qid)
   193  		}()
   194  	} else {
   195  		log.Debug("PersistableChannelQueue: %s Skipping running the empty level queue", q.delayedStarter.name)
   196  		q.internal.(*LevelQueue).Shutdown()
   197  		GetManager().Remove(q.internal.(*LevelQueue).qid)
   198  	}
   199  }
   200  
   201  // Flush flushes the queue and blocks till the queue is empty
   202  func (q *PersistableChannelQueue) Flush(timeout time.Duration) error {
   203  	var ctx context.Context
   204  	var cancel context.CancelFunc
   205  	if timeout > 0 {
   206  		ctx, cancel = context.WithTimeout(context.Background(), timeout)
   207  	} else {
   208  		ctx, cancel = context.WithCancel(context.Background())
   209  	}
   210  	defer cancel()
   211  	return q.FlushWithContext(ctx)
   212  }
   213  
   214  // FlushWithContext flushes the queue and blocks till the queue is empty
   215  func (q *PersistableChannelQueue) FlushWithContext(ctx context.Context) error {
   216  	errChan := make(chan error, 1)
   217  	go func() {
   218  		errChan <- q.channelQueue.FlushWithContext(ctx)
   219  	}()
   220  	go func() {
   221  		q.lock.Lock()
   222  		if q.internal == nil {
   223  			q.lock.Unlock()
   224  			errChan <- fmt.Errorf("not ready to flush internal queue %s yet", q.Name())
   225  			return
   226  		}
   227  		q.lock.Unlock()
   228  		errChan <- q.internal.FlushWithContext(ctx)
   229  	}()
   230  	err1 := <-errChan
   231  	err2 := <-errChan
   232  
   233  	if err1 != nil {
   234  		return err1
   235  	}
   236  	return err2
   237  }
   238  
   239  // IsEmpty checks if a queue is empty
   240  func (q *PersistableChannelQueue) IsEmpty() bool {
   241  	if !q.channelQueue.IsEmpty() {
   242  		return false
   243  	}
   244  	q.lock.Lock()
   245  	defer q.lock.Unlock()
   246  	if q.internal == nil {
   247  		return false
   248  	}
   249  	return q.internal.IsEmpty()
   250  }
   251  
   252  // IsPaused returns if the pool is paused
   253  func (q *PersistableChannelQueue) IsPaused() bool {
   254  	return q.channelQueue.IsPaused()
   255  }
   256  
   257  // IsPausedIsResumed returns if the pool is paused and a channel that is closed when it is resumed
   258  func (q *PersistableChannelQueue) IsPausedIsResumed() (<-chan struct{}, <-chan struct{}) {
   259  	return q.channelQueue.IsPausedIsResumed()
   260  }
   261  
   262  // Pause pauses the WorkerPool
   263  func (q *PersistableChannelQueue) Pause() {
   264  	q.channelQueue.Pause()
   265  	q.lock.Lock()
   266  	defer q.lock.Unlock()
   267  	if q.internal == nil {
   268  		return
   269  	}
   270  
   271  	pausable, ok := q.internal.(Pausable)
   272  	if !ok {
   273  		return
   274  	}
   275  	pausable.Pause()
   276  }
   277  
   278  // Resume resumes the WorkerPool
   279  func (q *PersistableChannelQueue) Resume() {
   280  	q.channelQueue.Resume()
   281  	q.lock.Lock()
   282  	defer q.lock.Unlock()
   283  	if q.internal == nil {
   284  		return
   285  	}
   286  
   287  	pausable, ok := q.internal.(Pausable)
   288  	if !ok {
   289  		return
   290  	}
   291  	pausable.Resume()
   292  }
   293  
   294  // Shutdown processing this queue
   295  func (q *PersistableChannelQueue) Shutdown() {
   296  	log.Trace("PersistableChannelQueue: %s Shutting down", q.delayedStarter.name)
   297  	q.lock.Lock()
   298  
   299  	select {
   300  	case <-q.closed:
   301  		q.lock.Unlock()
   302  		return
   303  	default:
   304  	}
   305  	q.channelQueue.Shutdown()
   306  	if q.internal != nil {
   307  		q.internal.(*LevelQueue).Shutdown()
   308  	}
   309  	close(q.closed)
   310  	q.lock.Unlock()
   311  
   312  	log.Trace("PersistableChannelQueue: %s Cancelling pools", q.delayedStarter.name)
   313  	q.channelQueue.baseCtxCancel()
   314  	q.internal.(*LevelQueue).baseCtxCancel()
   315  	log.Trace("PersistableChannelQueue: %s Waiting til done", q.delayedStarter.name)
   316  	q.channelQueue.Wait()
   317  	q.internal.(*LevelQueue).Wait()
   318  	// Redirect all remaining data in the chan to the internal channel
   319  	log.Trace("PersistableChannelQueue: %s Redirecting remaining data", q.delayedStarter.name)
   320  	close(q.channelQueue.dataChan)
   321  	for data := range q.channelQueue.dataChan {
   322  		_ = q.internal.Push(data)
   323  		atomic.AddInt64(&q.channelQueue.numInQueue, -1)
   324  	}
   325  	log.Trace("PersistableChannelQueue: %s Done Redirecting remaining data", q.delayedStarter.name)
   326  
   327  	log.Debug("PersistableChannelQueue: %s Shutdown", q.delayedStarter.name)
   328  }
   329  
   330  // Terminate this queue and close the queue
   331  func (q *PersistableChannelQueue) Terminate() {
   332  	log.Trace("PersistableChannelQueue: %s Terminating", q.delayedStarter.name)
   333  	q.Shutdown()
   334  	q.lock.Lock()
   335  	defer q.lock.Unlock()
   336  	q.channelQueue.Terminate()
   337  	if q.internal != nil {
   338  		q.internal.(*LevelQueue).Terminate()
   339  	}
   340  	log.Debug("PersistableChannelQueue: %s Terminated", q.delayedStarter.name)
   341  }
   342  
   343  func init() {
   344  	queuesMap[PersistableChannelQueueType] = NewPersistableChannelQueue
   345  }