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