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 }