github.com/gitbundle/modules@v0.0.0-20231025071548-85b91c5c3b01/queue/queue_disk_channel.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 "runtime/pprof" 12 "sync" 13 "sync/atomic" 14 "time" 15 16 "github.com/gitbundle/modules/log" 17 ) 18 19 // PersistableChannelQueueType is the type for persistable queue 20 const PersistableChannelQueueType Type = "persistable-channel" 21 22 // PersistableChannelQueueConfiguration is the configuration for a PersistableChannelQueue 23 type PersistableChannelQueueConfiguration struct { 24 Name string 25 DataDir string 26 BatchLength int 27 QueueLength int 28 Timeout time.Duration 29 MaxAttempts int 30 Workers int 31 MaxWorkers int 32 BlockTimeout time.Duration 33 BoostTimeout time.Duration 34 BoostWorkers int 35 } 36 37 // PersistableChannelQueue wraps a channel queue and level queue together 38 // The disk level queue will be used to store data at shutdown and terminate - and will be restored 39 // on start up. 40 type PersistableChannelQueue struct { 41 channelQueue *ChannelQueue 42 delayedStarter 43 lock sync.Mutex 44 closed chan struct{} 45 } 46 47 // NewPersistableChannelQueue creates a wrapped batched channel queue with persistable level queue backend when shutting down 48 // This differs from a wrapped queue in that the persistent queue is only used to persist at shutdown/terminate 49 func NewPersistableChannelQueue(handle HandlerFunc, cfg, exemplar interface{}) (Queue, error) { 50 configInterface, err := toConfig(PersistableChannelQueueConfiguration{}, cfg) 51 if err != nil { 52 return nil, err 53 } 54 config := configInterface.(PersistableChannelQueueConfiguration) 55 56 queue := &PersistableChannelQueue{ 57 closed: make(chan struct{}), 58 } 59 60 wrappedHandle := func(data ...Data) (failed []Data) { 61 for _, unhandled := range handle(data...) { 62 if fail := queue.PushBack(unhandled); fail != nil { 63 failed = append(failed, fail) 64 } 65 } 66 return 67 } 68 69 channelQueue, err := NewChannelQueue(wrappedHandle, ChannelQueueConfiguration{ 70 WorkerPoolConfiguration: WorkerPoolConfiguration{ 71 QueueLength: config.QueueLength, 72 BatchLength: config.BatchLength, 73 BlockTimeout: config.BlockTimeout, 74 BoostTimeout: config.BoostTimeout, 75 BoostWorkers: config.BoostWorkers, 76 MaxWorkers: config.MaxWorkers, 77 Name: config.Name + "-channel", 78 }, 79 Workers: config.Workers, 80 }, exemplar) 81 if err != nil { 82 return nil, err 83 } 84 85 // the level backend only needs temporary workers to catch up with the previously dropped work 86 levelCfg := LevelQueueConfiguration{ 87 ByteFIFOQueueConfiguration: ByteFIFOQueueConfiguration{ 88 WorkerPoolConfiguration: WorkerPoolConfiguration{ 89 QueueLength: config.QueueLength, 90 BatchLength: config.BatchLength, 91 BlockTimeout: 1 * time.Second, 92 BoostTimeout: 5 * time.Minute, 93 BoostWorkers: 1, 94 MaxWorkers: 5, 95 Name: config.Name + "-level", 96 }, 97 Workers: 0, 98 }, 99 DataDir: config.DataDir, 100 } 101 102 levelQueue, err := NewLevelQueue(wrappedHandle, levelCfg, exemplar) 103 if err == nil { 104 queue.channelQueue = channelQueue.(*ChannelQueue) 105 queue.delayedStarter = delayedStarter{ 106 internal: levelQueue.(*LevelQueue), 107 name: config.Name, 108 } 109 _ = GetManager().Add(queue, PersistableChannelQueueType, config, exemplar) 110 return queue, nil 111 } 112 if IsErrInvalidConfiguration(err) { 113 // Retrying ain't gonna make this any better... 114 return nil, ErrInvalidConfiguration{cfg: cfg} 115 } 116 117 queue.channelQueue = channelQueue.(*ChannelQueue) 118 queue.delayedStarter = delayedStarter{ 119 cfg: levelCfg, 120 underlying: LevelQueueType, 121 timeout: config.Timeout, 122 maxAttempts: config.MaxAttempts, 123 name: config.Name, 124 } 125 _ = GetManager().Add(queue, PersistableChannelQueueType, config, exemplar) 126 return queue, nil 127 } 128 129 // Name returns the name of this queue 130 func (q *PersistableChannelQueue) Name() string { 131 return q.delayedStarter.name 132 } 133 134 // Push will push the indexer data to queue 135 func (q *PersistableChannelQueue) Push(data Data) error { 136 select { 137 case <-q.closed: 138 return q.internal.Push(data) 139 default: 140 return q.channelQueue.Push(data) 141 } 142 } 143 144 // PushBack will push the indexer data to queue 145 func (q *PersistableChannelQueue) PushBack(data Data) error { 146 select { 147 case <-q.closed: 148 if pbr, ok := q.internal.(PushBackable); ok { 149 return pbr.PushBack(data) 150 } 151 return q.internal.Push(data) 152 default: 153 return q.channelQueue.Push(data) 154 } 155 } 156 157 // Run starts to run the queue 158 func (q *PersistableChannelQueue) Run(atShutdown, atTerminate func(func())) { 159 pprof.SetGoroutineLabels(q.channelQueue.baseCtx) 160 log.Debug("PersistableChannelQueue: %s Starting", q.delayedStarter.name) 161 _ = q.channelQueue.AddWorkers(q.channelQueue.workers, 0) 162 163 q.lock.Lock() 164 if q.internal == nil { 165 err := q.setInternal(atShutdown, q.channelQueue.handle, q.channelQueue.exemplar) 166 q.lock.Unlock() 167 if err != nil { 168 log.Fatal("Unable to create internal queue for %s Error: %v", q.Name(), err) 169 return 170 } 171 } else { 172 q.lock.Unlock() 173 } 174 atShutdown(q.Shutdown) 175 atTerminate(q.Terminate) 176 177 if lq, ok := q.internal.(*LevelQueue); ok && lq.byteFIFO.Len(lq.shutdownCtx) != 0 { 178 // Just run the level queue - we shut it down once it's flushed 179 go q.internal.Run(func(_ func()) {}, func(_ func()) {}) 180 go func() { 181 for !q.IsEmpty() { 182 _ = q.internal.Flush(0) 183 select { 184 case <-time.After(100 * time.Millisecond): 185 case <-q.internal.(*LevelQueue).shutdownCtx.Done(): 186 log.Warn("LevelQueue: %s shut down before completely flushed", q.internal.(*LevelQueue).Name()) 187 return 188 } 189 } 190 log.Debug("LevelQueue: %s flushed so shutting down", q.internal.(*LevelQueue).Name()) 191 q.internal.(*LevelQueue).Shutdown() 192 GetManager().Remove(q.internal.(*LevelQueue).qid) 193 }() 194 } else { 195 log.Debug("PersistableChannelQueue: %s Skipping running the empty level queue", q.delayedStarter.name) 196 q.internal.(*LevelQueue).Shutdown() 197 GetManager().Remove(q.internal.(*LevelQueue).qid) 198 } 199 } 200 201 // Flush flushes the queue and blocks till the queue is empty 202 func (q *PersistableChannelQueue) Flush(timeout time.Duration) error { 203 var ctx context.Context 204 var cancel context.CancelFunc 205 if timeout > 0 { 206 ctx, cancel = context.WithTimeout(context.Background(), timeout) 207 } else { 208 ctx, cancel = context.WithCancel(context.Background()) 209 } 210 defer cancel() 211 return q.FlushWithContext(ctx) 212 } 213 214 // FlushWithContext flushes the queue and blocks till the queue is empty 215 func (q *PersistableChannelQueue) FlushWithContext(ctx context.Context) error { 216 errChan := make(chan error, 1) 217 go func() { 218 errChan <- q.channelQueue.FlushWithContext(ctx) 219 }() 220 go func() { 221 q.lock.Lock() 222 if q.internal == nil { 223 q.lock.Unlock() 224 errChan <- fmt.Errorf("not ready to flush internal queue %s yet", q.Name()) 225 return 226 } 227 q.lock.Unlock() 228 errChan <- q.internal.FlushWithContext(ctx) 229 }() 230 err1 := <-errChan 231 err2 := <-errChan 232 233 if err1 != nil { 234 return err1 235 } 236 return err2 237 } 238 239 // IsEmpty checks if a queue is empty 240 func (q *PersistableChannelQueue) IsEmpty() bool { 241 if !q.channelQueue.IsEmpty() { 242 return false 243 } 244 q.lock.Lock() 245 defer q.lock.Unlock() 246 if q.internal == nil { 247 return false 248 } 249 return q.internal.IsEmpty() 250 } 251 252 // IsPaused returns if the pool is paused 253 func (q *PersistableChannelQueue) IsPaused() bool { 254 return q.channelQueue.IsPaused() 255 } 256 257 // IsPausedIsResumed returns if the pool is paused and a channel that is closed when it is resumed 258 func (q *PersistableChannelQueue) IsPausedIsResumed() (<-chan struct{}, <-chan struct{}) { 259 return q.channelQueue.IsPausedIsResumed() 260 } 261 262 // Pause pauses the WorkerPool 263 func (q *PersistableChannelQueue) Pause() { 264 q.channelQueue.Pause() 265 q.lock.Lock() 266 defer q.lock.Unlock() 267 if q.internal == nil { 268 return 269 } 270 271 pausable, ok := q.internal.(Pausable) 272 if !ok { 273 return 274 } 275 pausable.Pause() 276 } 277 278 // Resume resumes the WorkerPool 279 func (q *PersistableChannelQueue) Resume() { 280 q.channelQueue.Resume() 281 q.lock.Lock() 282 defer q.lock.Unlock() 283 if q.internal == nil { 284 return 285 } 286 287 pausable, ok := q.internal.(Pausable) 288 if !ok { 289 return 290 } 291 pausable.Resume() 292 } 293 294 // Shutdown processing this queue 295 func (q *PersistableChannelQueue) Shutdown() { 296 log.Trace("PersistableChannelQueue: %s Shutting down", q.delayedStarter.name) 297 q.lock.Lock() 298 299 select { 300 case <-q.closed: 301 q.lock.Unlock() 302 return 303 default: 304 } 305 q.channelQueue.Shutdown() 306 if q.internal != nil { 307 q.internal.(*LevelQueue).Shutdown() 308 } 309 close(q.closed) 310 q.lock.Unlock() 311 312 log.Trace("PersistableChannelQueue: %s Cancelling pools", q.delayedStarter.name) 313 q.channelQueue.baseCtxCancel() 314 q.internal.(*LevelQueue).baseCtxCancel() 315 log.Trace("PersistableChannelQueue: %s Waiting til done", q.delayedStarter.name) 316 q.channelQueue.Wait() 317 q.internal.(*LevelQueue).Wait() 318 // Redirect all remaining data in the chan to the internal channel 319 log.Trace("PersistableChannelQueue: %s Redirecting remaining data", q.delayedStarter.name) 320 close(q.channelQueue.dataChan) 321 for data := range q.channelQueue.dataChan { 322 _ = q.internal.Push(data) 323 atomic.AddInt64(&q.channelQueue.numInQueue, -1) 324 } 325 log.Trace("PersistableChannelQueue: %s Done Redirecting remaining data", q.delayedStarter.name) 326 327 log.Debug("PersistableChannelQueue: %s Shutdown", q.delayedStarter.name) 328 } 329 330 // Terminate this queue and close the queue 331 func (q *PersistableChannelQueue) Terminate() { 332 log.Trace("PersistableChannelQueue: %s Terminating", q.delayedStarter.name) 333 q.Shutdown() 334 q.lock.Lock() 335 defer q.lock.Unlock() 336 q.channelQueue.Terminate() 337 if q.internal != nil { 338 q.internal.(*LevelQueue).Terminate() 339 } 340 log.Debug("PersistableChannelQueue: %s Terminated", q.delayedStarter.name) 341 } 342 343 func init() { 344 queuesMap[PersistableChannelQueueType] = NewPersistableChannelQueue 345 }