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 }