github.com/gitbundle/modules@v0.0.0-20231025071548-85b91c5c3b01/queue/unique_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"
    13  	"sync/atomic"
    14  	"time"
    15  
    16  	"github.com/gitbundle/modules/json"
    17  	"github.com/gitbundle/modules/log"
    18  )
    19  
    20  // ChannelUniqueQueueType is the type for channel queue
    21  const ChannelUniqueQueueType Type = "unique-channel"
    22  
    23  // ChannelUniqueQueueConfiguration is the configuration for a ChannelUniqueQueue
    24  type ChannelUniqueQueueConfiguration ChannelQueueConfiguration
    25  
    26  // ChannelUniqueQueue implements UniqueQueue
    27  //
    28  // It is basically a thin wrapper around a WorkerPool but keeps a store of
    29  // what has been pushed within a table.
    30  //
    31  // Please note that this Queue does not guarantee that a particular
    32  // task cannot be processed twice or more at the same time. Uniqueness is
    33  // only guaranteed whilst the task is waiting in the queue.
    34  type ChannelUniqueQueue struct {
    35  	*WorkerPool
    36  	lock               sync.Mutex
    37  	table              map[string]bool
    38  	shutdownCtx        context.Context
    39  	shutdownCtxCancel  context.CancelFunc
    40  	terminateCtx       context.Context
    41  	terminateCtxCancel context.CancelFunc
    42  	exemplar           interface{}
    43  	workers            int
    44  	name               string
    45  }
    46  
    47  // NewChannelUniqueQueue create a memory channel queue
    48  func NewChannelUniqueQueue(handle HandlerFunc, cfg, exemplar interface{}) (Queue, error) {
    49  	configInterface, err := toConfig(ChannelUniqueQueueConfiguration{}, cfg)
    50  	if err != nil {
    51  		return nil, err
    52  	}
    53  	config := configInterface.(ChannelUniqueQueueConfiguration)
    54  	if config.BatchLength == 0 {
    55  		config.BatchLength = 1
    56  	}
    57  
    58  	terminateCtx, terminateCtxCancel := context.WithCancel(context.Background())
    59  	shutdownCtx, shutdownCtxCancel := context.WithCancel(terminateCtx)
    60  
    61  	queue := &ChannelUniqueQueue{
    62  		table:              map[string]bool{},
    63  		shutdownCtx:        shutdownCtx,
    64  		shutdownCtxCancel:  shutdownCtxCancel,
    65  		terminateCtx:       terminateCtx,
    66  		terminateCtxCancel: terminateCtxCancel,
    67  		exemplar:           exemplar,
    68  		workers:            config.Workers,
    69  		name:               config.Name,
    70  	}
    71  	queue.WorkerPool = NewWorkerPool(func(data ...Data) (unhandled []Data) {
    72  		for _, datum := range data {
    73  			// No error is possible here because PushFunc ensures that this can be marshalled
    74  			bs, _ := json.Marshal(datum)
    75  
    76  			queue.lock.Lock()
    77  			delete(queue.table, string(bs))
    78  			queue.lock.Unlock()
    79  
    80  			if u := handle(datum); u != nil {
    81  				if queue.IsPaused() {
    82  					// We can only pushback to the channel if we're paused.
    83  					go func() {
    84  						if err := queue.Push(u[0]); err != nil {
    85  							log.Error("Unable to push back to queue %d. Error: %v", queue.qid, err)
    86  						}
    87  					}()
    88  				} else {
    89  					unhandled = append(unhandled, u...)
    90  				}
    91  			}
    92  		}
    93  		return unhandled
    94  	}, config.WorkerPoolConfiguration)
    95  
    96  	queue.qid = GetManager().Add(queue, ChannelUniqueQueueType, config, exemplar)
    97  	return queue, nil
    98  }
    99  
   100  // Run starts to run the queue
   101  func (q *ChannelUniqueQueue) Run(atShutdown, atTerminate func(func())) {
   102  	pprof.SetGoroutineLabels(q.baseCtx)
   103  	atShutdown(q.Shutdown)
   104  	atTerminate(q.Terminate)
   105  	log.Debug("ChannelUniqueQueue: %s Starting", q.name)
   106  	_ = q.AddWorkers(q.workers, 0)
   107  }
   108  
   109  // Push will push data into the queue if the data is not already in the queue
   110  func (q *ChannelUniqueQueue) Push(data Data) error {
   111  	return q.PushFunc(data, nil)
   112  }
   113  
   114  // PushFunc will push data into the queue
   115  func (q *ChannelUniqueQueue) PushFunc(data Data, fn func() error) error {
   116  	if !assignableTo(data, q.exemplar) {
   117  		return fmt.Errorf("unable to assign data: %v to same type as exemplar: %v in queue: %s", data, q.exemplar, q.name)
   118  	}
   119  
   120  	bs, err := json.Marshal(data)
   121  	if err != nil {
   122  		return err
   123  	}
   124  	q.lock.Lock()
   125  	locked := true
   126  	defer func() {
   127  		if locked {
   128  			q.lock.Unlock()
   129  		}
   130  	}()
   131  	if _, ok := q.table[string(bs)]; ok {
   132  		return ErrAlreadyInQueue
   133  	}
   134  	// FIXME: We probably need to implement some sort of limit here
   135  	// If the downstream queue blocks this table will grow without limit
   136  	q.table[string(bs)] = true
   137  	if fn != nil {
   138  		err := fn()
   139  		if err != nil {
   140  			delete(q.table, string(bs))
   141  			return err
   142  		}
   143  	}
   144  	locked = false
   145  	q.lock.Unlock()
   146  	q.WorkerPool.Push(data)
   147  	return nil
   148  }
   149  
   150  // Has checks if the data is in the queue
   151  func (q *ChannelUniqueQueue) Has(data Data) (bool, error) {
   152  	bs, err := json.Marshal(data)
   153  	if err != nil {
   154  		return false, err
   155  	}
   156  
   157  	q.lock.Lock()
   158  	defer q.lock.Unlock()
   159  	_, has := q.table[string(bs)]
   160  	return has, nil
   161  }
   162  
   163  // Flush flushes the channel with a timeout - the Flush worker will be registered as a flush worker with the manager
   164  func (q *ChannelUniqueQueue) Flush(timeout time.Duration) error {
   165  	if q.IsPaused() {
   166  		return nil
   167  	}
   168  	ctx, cancel := q.commonRegisterWorkers(1, timeout, true)
   169  	defer cancel()
   170  	return q.FlushWithContext(ctx)
   171  }
   172  
   173  // FlushWithContext is very similar to CleanUp but it will return as soon as the dataChan is empty
   174  func (q *ChannelUniqueQueue) FlushWithContext(ctx context.Context) error {
   175  	log.Trace("ChannelUniqueQueue: %d Flush", q.qid)
   176  	paused, _ := q.IsPausedIsResumed()
   177  	for {
   178  		select {
   179  		case <-paused:
   180  			return nil
   181  		default:
   182  		}
   183  		select {
   184  		case data, ok := <-q.dataChan:
   185  			if !ok {
   186  				return nil
   187  			}
   188  			if unhandled := q.handle(data); unhandled != nil {
   189  				log.Error("Unhandled Data whilst flushing queue %d", q.qid)
   190  			}
   191  			atomic.AddInt64(&q.numInQueue, -1)
   192  		case <-q.baseCtx.Done():
   193  			return q.baseCtx.Err()
   194  		case <-ctx.Done():
   195  			return ctx.Err()
   196  		default:
   197  			return nil
   198  		}
   199  	}
   200  }
   201  
   202  // Shutdown processing from this queue
   203  func (q *ChannelUniqueQueue) Shutdown() {
   204  	log.Trace("ChannelUniqueQueue: %s Shutting down", q.name)
   205  	select {
   206  	case <-q.shutdownCtx.Done():
   207  		return
   208  	default:
   209  	}
   210  	go func() {
   211  		log.Trace("ChannelUniqueQueue: %s Flushing", q.name)
   212  		if err := q.FlushWithContext(q.terminateCtx); err != nil {
   213  			log.Warn("ChannelUniqueQueue: %s Terminated before completed flushing", q.name)
   214  			return
   215  		}
   216  		log.Debug("ChannelUniqueQueue: %s Flushed", q.name)
   217  	}()
   218  	q.shutdownCtxCancel()
   219  	log.Debug("ChannelUniqueQueue: %s Shutdown", q.name)
   220  }
   221  
   222  // Terminate this queue and close the queue
   223  func (q *ChannelUniqueQueue) Terminate() {
   224  	log.Trace("ChannelUniqueQueue: %s Terminating", q.name)
   225  	q.Shutdown()
   226  	select {
   227  	case <-q.terminateCtx.Done():
   228  		return
   229  	default:
   230  	}
   231  	q.terminateCtxCancel()
   232  	q.baseCtxFinished()
   233  	log.Debug("ChannelUniqueQueue: %s Terminated", q.name)
   234  }
   235  
   236  // Name returns the name of this queue
   237  func (q *ChannelUniqueQueue) Name() string {
   238  	return q.name
   239  }
   240  
   241  func init() {
   242  	queuesMap[ChannelUniqueQueueType] = NewChannelUniqueQueue
   243  }