code.gitea.io/gitea@v1.19.3/modules/queue/unique_queue_wrapped.go (about)

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