github.com/gitbundle/modules@v0.0.0-20231025071548-85b91c5c3b01/queue/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 "context" 10 "fmt" 11 "sync" 12 "sync/atomic" 13 "time" 14 15 "github.com/gitbundle/modules/log" 16 "github.com/gitbundle/modules/util" 17 ) 18 19 // WrappedQueueType is the type for a wrapped delayed starting queue 20 const WrappedQueueType Type = "wrapped" 21 22 // WrappedQueueConfiguration is the configuration for a WrappedQueue 23 type WrappedQueueConfiguration struct { 24 Underlying Type 25 Timeout time.Duration 26 MaxAttempts int 27 Config interface{} 28 QueueLength int 29 Name string 30 } 31 32 type delayedStarter struct { 33 internal Queue 34 underlying Type 35 cfg interface{} 36 timeout time.Duration 37 maxAttempts int 38 name string 39 } 40 41 // setInternal must be called with the lock locked. 42 func (q *delayedStarter) setInternal(atShutdown func(func()), handle HandlerFunc, exemplar interface{}) error { 43 var ctx context.Context 44 var cancel context.CancelFunc 45 if q.timeout > 0 { 46 ctx, cancel = context.WithTimeout(context.Background(), q.timeout) 47 } else { 48 ctx, cancel = context.WithCancel(context.Background()) 49 } 50 51 defer cancel() 52 // Ensure we also stop at shutdown 53 atShutdown(cancel) 54 55 i := 1 56 for q.internal == nil { 57 select { 58 case <-ctx.Done(): 59 cfg := q.cfg 60 if s, ok := cfg.([]byte); ok { 61 cfg = string(s) 62 } 63 return fmt.Errorf("timedout creating queue %v with cfg %#v in %s", q.underlying, cfg, q.name) 64 default: 65 queue, err := NewQueue(q.underlying, handle, q.cfg, exemplar) 66 if err == nil { 67 q.internal = queue 68 break 69 } 70 if err.Error() != "resource temporarily unavailable" { 71 if bs, ok := q.cfg.([]byte); ok { 72 log.Warn("[Attempt: %d] Failed to create queue: %v for %s cfg: %s error: %v", i, q.underlying, q.name, string(bs), err) 73 } else { 74 log.Warn("[Attempt: %d] Failed to create queue: %v for %s cfg: %#v error: %v", i, q.underlying, q.name, q.cfg, err) 75 } 76 } 77 i++ 78 if q.maxAttempts > 0 && i > q.maxAttempts { 79 if bs, ok := q.cfg.([]byte); ok { 80 return fmt.Errorf("unable to create queue %v for %s with cfg %s by max attempts: error: %v", q.underlying, q.name, string(bs), err) 81 } 82 return fmt.Errorf("unable to create queue %v for %s with cfg %#v by max attempts: error: %v", q.underlying, q.name, q.cfg, err) 83 } 84 sleepTime := 100 * time.Millisecond 85 if q.timeout > 0 && q.maxAttempts > 0 { 86 sleepTime = (q.timeout - 200*time.Millisecond) / time.Duration(q.maxAttempts) 87 } 88 t := time.NewTimer(sleepTime) 89 select { 90 case <-ctx.Done(): 91 util.StopTimer(t) 92 case <-t.C: 93 } 94 } 95 } 96 return nil 97 } 98 99 // WrappedQueue wraps a delayed starting queue 100 type WrappedQueue struct { 101 delayedStarter 102 lock sync.Mutex 103 handle HandlerFunc 104 exemplar interface{} 105 channel chan Data 106 numInQueue int64 107 } 108 109 // NewWrappedQueue will attempt to create a queue of the provided type, 110 // but if there is a problem creating this queue it will instead create 111 // a WrappedQueue with delayed startup of the queue instead and a 112 // channel which will be redirected to the queue 113 func NewWrappedQueue(handle HandlerFunc, cfg, exemplar interface{}) (Queue, error) { 114 configInterface, err := toConfig(WrappedQueueConfiguration{}, cfg) 115 if err != nil { 116 return nil, err 117 } 118 config := configInterface.(WrappedQueueConfiguration) 119 120 queue, err := NewQueue(config.Underlying, handle, config.Config, exemplar) 121 if err == nil { 122 // Just return the queue there is no need to wrap 123 return queue, nil 124 } 125 if IsErrInvalidConfiguration(err) { 126 // Retrying ain't gonna make this any better... 127 return nil, ErrInvalidConfiguration{cfg: cfg} 128 } 129 130 queue = &WrappedQueue{ 131 handle: handle, 132 channel: make(chan Data, config.QueueLength), 133 exemplar: exemplar, 134 delayedStarter: delayedStarter{ 135 cfg: config.Config, 136 underlying: config.Underlying, 137 timeout: config.Timeout, 138 maxAttempts: config.MaxAttempts, 139 name: config.Name, 140 }, 141 } 142 _ = GetManager().Add(queue, WrappedQueueType, config, exemplar) 143 return queue, nil 144 } 145 146 // Name returns the name of the queue 147 func (q *WrappedQueue) Name() string { 148 return q.name + "-wrapper" 149 } 150 151 // Push will push the data to the internal channel checking it against the exemplar 152 func (q *WrappedQueue) Push(data Data) error { 153 if !assignableTo(data, q.exemplar) { 154 return fmt.Errorf("unable to assign data: %v to same type as exemplar: %v in %s", data, q.exemplar, q.name) 155 } 156 atomic.AddInt64(&q.numInQueue, 1) 157 q.channel <- data 158 return nil 159 } 160 161 func (q *WrappedQueue) flushInternalWithContext(ctx context.Context) error { 162 q.lock.Lock() 163 if q.internal == nil { 164 q.lock.Unlock() 165 return fmt.Errorf("not ready to flush wrapped queue %s yet", q.Name()) 166 } 167 q.lock.Unlock() 168 select { 169 case <-ctx.Done(): 170 return ctx.Err() 171 default: 172 } 173 return q.internal.FlushWithContext(ctx) 174 } 175 176 // Flush flushes the queue and blocks till the queue is empty 177 func (q *WrappedQueue) Flush(timeout time.Duration) error { 178 var ctx context.Context 179 var cancel context.CancelFunc 180 if timeout > 0 { 181 ctx, cancel = context.WithTimeout(context.Background(), timeout) 182 } else { 183 ctx, cancel = context.WithCancel(context.Background()) 184 } 185 defer cancel() 186 return q.FlushWithContext(ctx) 187 } 188 189 // FlushWithContext implements the final part of Flushable 190 func (q *WrappedQueue) FlushWithContext(ctx context.Context) error { 191 log.Trace("WrappedQueue: %s FlushWithContext", q.Name()) 192 errChan := make(chan error, 1) 193 go func() { 194 errChan <- q.flushInternalWithContext(ctx) 195 close(errChan) 196 }() 197 198 select { 199 case err := <-errChan: 200 return err 201 case <-ctx.Done(): 202 go func() { 203 <-errChan 204 }() 205 return ctx.Err() 206 } 207 } 208 209 // IsEmpty checks whether the queue is empty 210 func (q *WrappedQueue) IsEmpty() bool { 211 if atomic.LoadInt64(&q.numInQueue) != 0 { 212 return false 213 } 214 q.lock.Lock() 215 defer q.lock.Unlock() 216 if q.internal == nil { 217 return false 218 } 219 return q.internal.IsEmpty() 220 } 221 222 // Run starts to run the queue and attempts to create the internal queue 223 func (q *WrappedQueue) Run(atShutdown, atTerminate func(func())) { 224 log.Debug("WrappedQueue: %s Starting", q.name) 225 q.lock.Lock() 226 if q.internal == nil { 227 err := q.setInternal(atShutdown, q.handle, q.exemplar) 228 q.lock.Unlock() 229 if err != nil { 230 log.Fatal("Unable to set the internal queue for %s Error: %v", q.Name(), err) 231 return 232 } 233 go func() { 234 for data := range q.channel { 235 _ = q.internal.Push(data) 236 atomic.AddInt64(&q.numInQueue, -1) 237 } 238 }() 239 } else { 240 q.lock.Unlock() 241 } 242 243 q.internal.Run(atShutdown, atTerminate) 244 log.Trace("WrappedQueue: %s Done", q.name) 245 } 246 247 // Shutdown this queue and stop processing 248 func (q *WrappedQueue) Shutdown() { 249 log.Trace("WrappedQueue: %s Shutting down", q.name) 250 q.lock.Lock() 251 defer q.lock.Unlock() 252 if q.internal == nil { 253 return 254 } 255 if shutdownable, ok := q.internal.(Shutdownable); ok { 256 shutdownable.Shutdown() 257 } 258 log.Debug("WrappedQueue: %s Shutdown", q.name) 259 } 260 261 // Terminate this queue and close the queue 262 func (q *WrappedQueue) Terminate() { 263 log.Trace("WrappedQueue: %s Terminating", q.name) 264 q.lock.Lock() 265 defer q.lock.Unlock() 266 if q.internal == nil { 267 return 268 } 269 if shutdownable, ok := q.internal.(Shutdownable); ok { 270 shutdownable.Terminate() 271 } 272 log.Debug("WrappedQueue: %s Terminated", q.name) 273 } 274 275 // IsPaused will return if the pool or queue is paused 276 func (q *WrappedQueue) IsPaused() bool { 277 q.lock.Lock() 278 defer q.lock.Unlock() 279 pausable, ok := q.internal.(Pausable) 280 return ok && pausable.IsPaused() 281 } 282 283 // Pause will pause the pool or queue 284 func (q *WrappedQueue) Pause() { 285 q.lock.Lock() 286 defer q.lock.Unlock() 287 if pausable, ok := q.internal.(Pausable); ok { 288 pausable.Pause() 289 } 290 } 291 292 // Resume will resume the pool or queue 293 func (q *WrappedQueue) Resume() { 294 q.lock.Lock() 295 defer q.lock.Unlock() 296 if pausable, ok := q.internal.(Pausable); ok { 297 pausable.Resume() 298 } 299 } 300 301 // IsPausedIsResumed will return a bool indicating if the pool or queue is paused and a channel that will be closed when it is resumed 302 func (q *WrappedQueue) IsPausedIsResumed() (paused, resumed <-chan struct{}) { 303 q.lock.Lock() 304 defer q.lock.Unlock() 305 if pausable, ok := q.internal.(Pausable); ok { 306 return pausable.IsPausedIsResumed() 307 } 308 return context.Background().Done(), closedChan 309 } 310 311 var closedChan chan struct{} 312 313 func init() { 314 queuesMap[WrappedQueueType] = NewWrappedQueue 315 closedChan = make(chan struct{}) 316 close(closedChan) 317 }