github.com/gitbundle/modules@v0.0.0-20231025071548-85b91c5c3b01/queue/queue_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/atomic" 13 "time" 14 15 "github.com/gitbundle/modules/log" 16 ) 17 18 // ChannelQueueType is the type for channel queue 19 const ChannelQueueType Type = "channel" 20 21 // ChannelQueueConfiguration is the configuration for a ChannelQueue 22 type ChannelQueueConfiguration struct { 23 WorkerPoolConfiguration 24 Workers int 25 } 26 27 // ChannelQueue implements Queue 28 // 29 // A channel queue is not persistable and does not shutdown or terminate cleanly 30 // It is basically a very thin wrapper around a WorkerPool 31 type ChannelQueue struct { 32 *WorkerPool 33 shutdownCtx context.Context 34 shutdownCtxCancel context.CancelFunc 35 terminateCtx context.Context 36 terminateCtxCancel context.CancelFunc 37 exemplar interface{} 38 workers int 39 name string 40 } 41 42 // NewChannelQueue creates a memory channel queue 43 func NewChannelQueue(handle HandlerFunc, cfg, exemplar interface{}) (Queue, error) { 44 configInterface, err := toConfig(ChannelQueueConfiguration{}, cfg) 45 if err != nil { 46 return nil, err 47 } 48 config := configInterface.(ChannelQueueConfiguration) 49 if config.BatchLength == 0 { 50 config.BatchLength = 1 51 } 52 53 terminateCtx, terminateCtxCancel := context.WithCancel(context.Background()) 54 shutdownCtx, shutdownCtxCancel := context.WithCancel(terminateCtx) 55 56 queue := &ChannelQueue{ 57 shutdownCtx: shutdownCtx, 58 shutdownCtxCancel: shutdownCtxCancel, 59 terminateCtx: terminateCtx, 60 terminateCtxCancel: terminateCtxCancel, 61 exemplar: exemplar, 62 workers: config.Workers, 63 name: config.Name, 64 } 65 queue.WorkerPool = NewWorkerPool(func(data ...Data) []Data { 66 unhandled := handle(data...) 67 if len(unhandled) > 0 { 68 // We can only pushback to the channel if we're paused. 69 if queue.IsPaused() { 70 atomic.AddInt64(&queue.numInQueue, int64(len(unhandled))) 71 go func() { 72 for _, datum := range data { 73 queue.dataChan <- datum 74 } 75 }() 76 return nil 77 } 78 } 79 return unhandled 80 }, config.WorkerPoolConfiguration) 81 82 queue.qid = GetManager().Add(queue, ChannelQueueType, config, exemplar) 83 return queue, nil 84 } 85 86 // Run starts to run the queue 87 func (q *ChannelQueue) Run(atShutdown, atTerminate func(func())) { 88 pprof.SetGoroutineLabels(q.baseCtx) 89 atShutdown(q.Shutdown) 90 atTerminate(q.Terminate) 91 log.Debug("ChannelQueue: %s Starting", q.name) 92 _ = q.AddWorkers(q.workers, 0) 93 } 94 95 // Push will push data into the queue 96 func (q *ChannelQueue) Push(data Data) error { 97 if !assignableTo(data, q.exemplar) { 98 return fmt.Errorf("unable to assign data: %v to same type as exemplar: %v in queue: %s", data, q.exemplar, q.name) 99 } 100 q.WorkerPool.Push(data) 101 return nil 102 } 103 104 // Flush flushes the channel with a timeout - the Flush worker will be registered as a flush worker with the manager 105 func (q *ChannelQueue) Flush(timeout time.Duration) error { 106 if q.IsPaused() { 107 return nil 108 } 109 ctx, cancel := q.commonRegisterWorkers(1, timeout, true) 110 defer cancel() 111 return q.FlushWithContext(ctx) 112 } 113 114 // FlushWithContext is very similar to CleanUp but it will return as soon as the dataChan is empty 115 func (q *ChannelQueue) FlushWithContext(ctx context.Context) error { 116 log.Trace("ChannelQueue: %d Flush", q.qid) 117 paused, _ := q.IsPausedIsResumed() 118 for { 119 select { 120 case <-paused: 121 return nil 122 case data, ok := <-q.dataChan: 123 if !ok { 124 return nil 125 } 126 if unhandled := q.handle(data); unhandled != nil { 127 log.Error("Unhandled Data whilst flushing queue %d", q.qid) 128 } 129 atomic.AddInt64(&q.numInQueue, -1) 130 case <-q.baseCtx.Done(): 131 return q.baseCtx.Err() 132 case <-ctx.Done(): 133 return ctx.Err() 134 default: 135 return nil 136 } 137 } 138 } 139 140 // Shutdown processing from this queue 141 func (q *ChannelQueue) Shutdown() { 142 q.lock.Lock() 143 defer q.lock.Unlock() 144 select { 145 case <-q.shutdownCtx.Done(): 146 log.Trace("ChannelQueue: %s Already Shutting down", q.name) 147 return 148 default: 149 } 150 log.Trace("ChannelQueue: %s Shutting down", q.name) 151 go func() { 152 log.Trace("ChannelQueue: %s Flushing", q.name) 153 // We can't use Cleanup here because that will close the channel 154 if err := q.FlushWithContext(q.terminateCtx); err != nil { 155 log.Warn("ChannelQueue: %s Terminated before completed flushing", q.name) 156 return 157 } 158 log.Debug("ChannelQueue: %s Flushed", q.name) 159 }() 160 q.shutdownCtxCancel() 161 log.Debug("ChannelQueue: %s Shutdown", q.name) 162 } 163 164 // Terminate this queue and close the queue 165 func (q *ChannelQueue) Terminate() { 166 log.Trace("ChannelQueue: %s Terminating", q.name) 167 q.Shutdown() 168 select { 169 case <-q.terminateCtx.Done(): 170 return 171 default: 172 } 173 q.terminateCtxCancel() 174 q.baseCtxFinished() 175 log.Debug("ChannelQueue: %s Terminated", q.name) 176 } 177 178 // Name returns the name of this queue 179 func (q *ChannelQueue) Name() string { 180 return q.name 181 } 182 183 func init() { 184 queuesMap[ChannelQueueType] = NewChannelQueue 185 }