github.com/gitbundle/modules@v0.0.0-20231025071548-85b91c5c3b01/queue/unique_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  	"fmt"
    10  	"sync"
    11  	"time"
    12  )
    13  
    14  // WrappedUniqueQueueType is the type for a wrapped delayed starting queue
    15  const WrappedUniqueQueueType Type = "unique-wrapped"
    16  
    17  // WrappedUniqueQueueConfiguration is the configuration for a WrappedUniqueQueue
    18  type WrappedUniqueQueueConfiguration struct {
    19  	Underlying  Type
    20  	Timeout     time.Duration
    21  	MaxAttempts int
    22  	Config      interface{}
    23  	QueueLength int
    24  	Name        string
    25  }
    26  
    27  // WrappedUniqueQueue wraps a delayed starting unique queue
    28  type WrappedUniqueQueue struct {
    29  	*WrappedQueue
    30  	table map[Data]bool
    31  	tlock sync.Mutex
    32  	ready bool
    33  }
    34  
    35  // NewWrappedUniqueQueue will attempt to create a unique queue of the provided type,
    36  // but if there is a problem creating this queue it will instead create
    37  // a WrappedUniqueQueue with delayed startup of the queue instead and a
    38  // channel which will be redirected to the queue
    39  //
    40  // Please note that this Queue does not guarantee that a particular
    41  // task cannot be processed twice or more at the same time. Uniqueness is
    42  // only guaranteed whilst the task is waiting in the queue.
    43  func NewWrappedUniqueQueue(handle HandlerFunc, cfg, exemplar interface{}) (Queue, error) {
    44  	configInterface, err := toConfig(WrappedUniqueQueueConfiguration{}, cfg)
    45  	if err != nil {
    46  		return nil, err
    47  	}
    48  	config := configInterface.(WrappedUniqueQueueConfiguration)
    49  
    50  	queue, err := NewQueue(config.Underlying, handle, config.Config, exemplar)
    51  	if err == nil {
    52  		// Just return the queue there is no need to wrap
    53  		return queue, nil
    54  	}
    55  	if IsErrInvalidConfiguration(err) {
    56  		// Retrying ain't gonna make this any better...
    57  		return nil, ErrInvalidConfiguration{cfg: cfg}
    58  	}
    59  
    60  	wrapped := &WrappedUniqueQueue{
    61  		WrappedQueue: &WrappedQueue{
    62  			channel:  make(chan Data, config.QueueLength),
    63  			exemplar: exemplar,
    64  			delayedStarter: delayedStarter{
    65  				cfg:         config.Config,
    66  				underlying:  config.Underlying,
    67  				timeout:     config.Timeout,
    68  				maxAttempts: config.MaxAttempts,
    69  				name:        config.Name,
    70  			},
    71  		},
    72  		table: map[Data]bool{},
    73  	}
    74  
    75  	// wrapped.handle is passed to the delayedStarting internal queue and is run to handle
    76  	// data passed to
    77  	wrapped.handle = func(data ...Data) (unhandled []Data) {
    78  		for _, datum := range data {
    79  			wrapped.tlock.Lock()
    80  			if !wrapped.ready {
    81  				delete(wrapped.table, data)
    82  				// If our table is empty all of the requests we have buffered between the
    83  				// wrapper queue starting and the internal queue starting have been handled.
    84  				// We can stop buffering requests in our local table and just pass Push
    85  				// direct to the internal queue
    86  				if len(wrapped.table) == 0 {
    87  					wrapped.ready = true
    88  				}
    89  			}
    90  			wrapped.tlock.Unlock()
    91  			if u := handle(datum); u != nil {
    92  				unhandled = append(unhandled, u...)
    93  			}
    94  		}
    95  		return unhandled
    96  	}
    97  	_ = GetManager().Add(queue, WrappedUniqueQueueType, config, exemplar)
    98  	return wrapped, nil
    99  }
   100  
   101  // Push will push the data to the internal channel checking it against the exemplar
   102  func (q *WrappedUniqueQueue) Push(data Data) error {
   103  	return q.PushFunc(data, nil)
   104  }
   105  
   106  // PushFunc will push the data to the internal channel checking it against the exemplar
   107  func (q *WrappedUniqueQueue) PushFunc(data Data, fn func() error) error {
   108  	if !assignableTo(data, q.exemplar) {
   109  		return fmt.Errorf("unable to assign data: %v to same type as exemplar: %v in %s", data, q.exemplar, q.name)
   110  	}
   111  
   112  	q.tlock.Lock()
   113  	if q.ready {
   114  		// ready means our table is empty and all of the requests we have buffered between the
   115  		// wrapper queue starting and the internal queue starting have been handled.
   116  		// We can stop buffering requests in our local table and just pass Push
   117  		// direct to the internal queue
   118  		q.tlock.Unlock()
   119  		return q.internal.(UniqueQueue).PushFunc(data, fn)
   120  	}
   121  
   122  	locked := true
   123  	defer func() {
   124  		if locked {
   125  			q.tlock.Unlock()
   126  		}
   127  	}()
   128  	if _, ok := q.table[data]; ok {
   129  		return ErrAlreadyInQueue
   130  	}
   131  	// FIXME: We probably need to implement some sort of limit here
   132  	// If the downstream queue blocks this table will grow without limit
   133  	q.table[data] = true
   134  	if fn != nil {
   135  		err := fn()
   136  		if err != nil {
   137  			delete(q.table, data)
   138  			return err
   139  		}
   140  	}
   141  	locked = false
   142  	q.tlock.Unlock()
   143  
   144  	q.channel <- data
   145  	return nil
   146  }
   147  
   148  // Has checks if the data is in the queue
   149  func (q *WrappedUniqueQueue) Has(data Data) (bool, error) {
   150  	q.tlock.Lock()
   151  	defer q.tlock.Unlock()
   152  	if q.ready {
   153  		return q.internal.(UniqueQueue).Has(data)
   154  	}
   155  	_, has := q.table[data]
   156  	return has, nil
   157  }
   158  
   159  // IsEmpty checks whether the queue is empty
   160  func (q *WrappedUniqueQueue) IsEmpty() bool {
   161  	q.tlock.Lock()
   162  	if len(q.table) > 0 {
   163  		q.tlock.Unlock()
   164  		return false
   165  	}
   166  	if q.ready {
   167  		q.tlock.Unlock()
   168  		return q.internal.IsEmpty()
   169  	}
   170  	q.tlock.Unlock()
   171  	return false
   172  }
   173  
   174  func init() {
   175  	queuesMap[WrappedUniqueQueueType] = NewWrappedUniqueQueue
   176  }