github.com/gitbundle/modules@v0.0.0-20231025071548-85b91c5c3b01/queue/queue_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/atomic"
    13  	"time"
    14  
    15  	"github.com/gitbundle/modules/log"
    16  )
    17  
    18  // ChannelQueueType is the type for channel queue
    19  const ChannelQueueType Type = "channel"
    20  
    21  // ChannelQueueConfiguration is the configuration for a ChannelQueue
    22  type ChannelQueueConfiguration struct {
    23  	WorkerPoolConfiguration
    24  	Workers int
    25  }
    26  
    27  // ChannelQueue implements Queue
    28  //
    29  // A channel queue is not persistable and does not shutdown or terminate cleanly
    30  // It is basically a very thin wrapper around a WorkerPool
    31  type ChannelQueue struct {
    32  	*WorkerPool
    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  }
    41  
    42  // NewChannelQueue creates a memory channel queue
    43  func NewChannelQueue(handle HandlerFunc, cfg, exemplar interface{}) (Queue, error) {
    44  	configInterface, err := toConfig(ChannelQueueConfiguration{}, cfg)
    45  	if err != nil {
    46  		return nil, err
    47  	}
    48  	config := configInterface.(ChannelQueueConfiguration)
    49  	if config.BatchLength == 0 {
    50  		config.BatchLength = 1
    51  	}
    52  
    53  	terminateCtx, terminateCtxCancel := context.WithCancel(context.Background())
    54  	shutdownCtx, shutdownCtxCancel := context.WithCancel(terminateCtx)
    55  
    56  	queue := &ChannelQueue{
    57  		shutdownCtx:        shutdownCtx,
    58  		shutdownCtxCancel:  shutdownCtxCancel,
    59  		terminateCtx:       terminateCtx,
    60  		terminateCtxCancel: terminateCtxCancel,
    61  		exemplar:           exemplar,
    62  		workers:            config.Workers,
    63  		name:               config.Name,
    64  	}
    65  	queue.WorkerPool = NewWorkerPool(func(data ...Data) []Data {
    66  		unhandled := handle(data...)
    67  		if len(unhandled) > 0 {
    68  			// We can only pushback to the channel if we're paused.
    69  			if queue.IsPaused() {
    70  				atomic.AddInt64(&queue.numInQueue, int64(len(unhandled)))
    71  				go func() {
    72  					for _, datum := range data {
    73  						queue.dataChan <- datum
    74  					}
    75  				}()
    76  				return nil
    77  			}
    78  		}
    79  		return unhandled
    80  	}, config.WorkerPoolConfiguration)
    81  
    82  	queue.qid = GetManager().Add(queue, ChannelQueueType, config, exemplar)
    83  	return queue, nil
    84  }
    85  
    86  // Run starts to run the queue
    87  func (q *ChannelQueue) Run(atShutdown, atTerminate func(func())) {
    88  	pprof.SetGoroutineLabels(q.baseCtx)
    89  	atShutdown(q.Shutdown)
    90  	atTerminate(q.Terminate)
    91  	log.Debug("ChannelQueue: %s Starting", q.name)
    92  	_ = q.AddWorkers(q.workers, 0)
    93  }
    94  
    95  // Push will push data into the queue
    96  func (q *ChannelQueue) Push(data Data) error {
    97  	if !assignableTo(data, q.exemplar) {
    98  		return fmt.Errorf("unable to assign data: %v to same type as exemplar: %v in queue: %s", data, q.exemplar, q.name)
    99  	}
   100  	q.WorkerPool.Push(data)
   101  	return nil
   102  }
   103  
   104  // Flush flushes the channel with a timeout - the Flush worker will be registered as a flush worker with the manager
   105  func (q *ChannelQueue) Flush(timeout time.Duration) error {
   106  	if q.IsPaused() {
   107  		return nil
   108  	}
   109  	ctx, cancel := q.commonRegisterWorkers(1, timeout, true)
   110  	defer cancel()
   111  	return q.FlushWithContext(ctx)
   112  }
   113  
   114  // FlushWithContext is very similar to CleanUp but it will return as soon as the dataChan is empty
   115  func (q *ChannelQueue) FlushWithContext(ctx context.Context) error {
   116  	log.Trace("ChannelQueue: %d Flush", q.qid)
   117  	paused, _ := q.IsPausedIsResumed()
   118  	for {
   119  		select {
   120  		case <-paused:
   121  			return nil
   122  		case data, ok := <-q.dataChan:
   123  			if !ok {
   124  				return nil
   125  			}
   126  			if unhandled := q.handle(data); unhandled != nil {
   127  				log.Error("Unhandled Data whilst flushing queue %d", q.qid)
   128  			}
   129  			atomic.AddInt64(&q.numInQueue, -1)
   130  		case <-q.baseCtx.Done():
   131  			return q.baseCtx.Err()
   132  		case <-ctx.Done():
   133  			return ctx.Err()
   134  		default:
   135  			return nil
   136  		}
   137  	}
   138  }
   139  
   140  // Shutdown processing from this queue
   141  func (q *ChannelQueue) Shutdown() {
   142  	q.lock.Lock()
   143  	defer q.lock.Unlock()
   144  	select {
   145  	case <-q.shutdownCtx.Done():
   146  		log.Trace("ChannelQueue: %s Already Shutting down", q.name)
   147  		return
   148  	default:
   149  	}
   150  	log.Trace("ChannelQueue: %s Shutting down", q.name)
   151  	go func() {
   152  		log.Trace("ChannelQueue: %s Flushing", q.name)
   153  		// We can't use Cleanup here because that will close the channel
   154  		if err := q.FlushWithContext(q.terminateCtx); err != nil {
   155  			log.Warn("ChannelQueue: %s Terminated before completed flushing", q.name)
   156  			return
   157  		}
   158  		log.Debug("ChannelQueue: %s Flushed", q.name)
   159  	}()
   160  	q.shutdownCtxCancel()
   161  	log.Debug("ChannelQueue: %s Shutdown", q.name)
   162  }
   163  
   164  // Terminate this queue and close the queue
   165  func (q *ChannelQueue) Terminate() {
   166  	log.Trace("ChannelQueue: %s Terminating", q.name)
   167  	q.Shutdown()
   168  	select {
   169  	case <-q.terminateCtx.Done():
   170  		return
   171  	default:
   172  	}
   173  	q.terminateCtxCancel()
   174  	q.baseCtxFinished()
   175  	log.Debug("ChannelQueue: %s Terminated", q.name)
   176  }
   177  
   178  // Name returns the name of this queue
   179  func (q *ChannelQueue) Name() string {
   180  	return q.name
   181  }
   182  
   183  func init() {
   184  	queuesMap[ChannelQueueType] = NewChannelQueue
   185  }