go.temporal.io/server@v1.23.0/common/tasks/fifo_scheduler.go (about) 1 // The MIT License 2 // 3 // Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. 4 // 5 // Copyright (c) 2020 Uber Technologies, Inc. 6 // 7 // Permission is hereby granted, free of charge, to any person obtaining a copy 8 // of this software and associated documentation files (the "Software"), to deal 9 // in the Software without restriction, including without limitation the rights 10 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 // copies of the Software, and to permit persons to whom the Software is 12 // furnished to do so, subject to the following conditions: 13 // 14 // The above copyright notice and this permission notice shall be included in 15 // all copies or substantial portions of the Software. 16 // 17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 // THE SOFTWARE. 24 25 package tasks 26 27 import ( 28 "sync" 29 "sync/atomic" 30 "time" 31 32 "go.temporal.io/server/common" 33 "go.temporal.io/server/common/backoff" 34 "go.temporal.io/server/common/dynamicconfig" 35 "go.temporal.io/server/common/log" 36 "go.temporal.io/server/common/log/tag" 37 ) 38 39 const ( 40 defaultMonitorTickerDuration = time.Minute 41 defaultMonitorTickerJitter = 0.15 42 ) 43 44 var _ Scheduler[Task] = (*FIFOScheduler[Task])(nil) 45 46 type ( 47 // FIFOSchedulerOptions is the configs for FIFOScheduler 48 FIFOSchedulerOptions struct { 49 QueueSize int 50 WorkerCount dynamicconfig.IntPropertyFn 51 } 52 53 FIFOScheduler[T Task] struct { 54 status int32 55 options *FIFOSchedulerOptions 56 57 logger log.Logger 58 59 tasksChan chan T 60 shutdownChan chan struct{} 61 shutdownWG sync.WaitGroup 62 workerShutdownCh []chan struct{} 63 } 64 ) 65 66 // NewFIFOScheduler creates a new FIFOScheduler 67 func NewFIFOScheduler[T Task]( 68 options *FIFOSchedulerOptions, 69 logger log.Logger, 70 ) *FIFOScheduler[T] { 71 return &FIFOScheduler[T]{ 72 status: common.DaemonStatusInitialized, 73 options: options, 74 75 logger: logger, 76 77 tasksChan: make(chan T, options.QueueSize), 78 shutdownChan: make(chan struct{}), 79 } 80 } 81 82 func (f *FIFOScheduler[T]) Start() { 83 if !atomic.CompareAndSwapInt32( 84 &f.status, 85 common.DaemonStatusInitialized, 86 common.DaemonStatusStarted, 87 ) { 88 return 89 } 90 91 f.startWorkers(f.options.WorkerCount()) 92 93 f.shutdownWG.Add(1) 94 go f.workerMonitor() 95 96 f.logger.Info("fifo scheduler started") 97 } 98 99 func (f *FIFOScheduler[T]) Stop() { 100 if !atomic.CompareAndSwapInt32( 101 &f.status, 102 common.DaemonStatusStarted, 103 common.DaemonStatusStopped, 104 ) { 105 return 106 } 107 108 close(f.shutdownChan) 109 // must be called after the close of the shutdownChan 110 f.drainTasks() 111 112 go func() { 113 if success := common.AwaitWaitGroup(&f.shutdownWG, time.Minute); !success { 114 f.logger.Warn("fifo scheduler timed out waiting for workers") 115 } 116 }() 117 f.logger.Info("fifo scheduler stopped") 118 } 119 120 func (f *FIFOScheduler[T]) Submit(task T) { 121 f.tasksChan <- task 122 if f.isStopped() { 123 f.drainTasks() 124 } 125 } 126 127 func (f *FIFOScheduler[T]) TrySubmit(task T) bool { 128 select { 129 case f.tasksChan <- task: 130 if f.isStopped() { 131 f.drainTasks() 132 } 133 return true 134 default: 135 return false 136 } 137 } 138 139 func (f *FIFOScheduler[T]) workerMonitor() { 140 defer f.shutdownWG.Done() 141 142 timer := time.NewTimer(backoff.Jitter(defaultMonitorTickerDuration, defaultMonitorTickerJitter)) 143 defer timer.Stop() 144 145 for { 146 select { 147 case <-f.shutdownChan: 148 f.stopWorkers(len(f.workerShutdownCh)) 149 return 150 case <-timer.C: 151 timer.Reset(backoff.Jitter(defaultMonitorTickerDuration, defaultMonitorTickerJitter)) 152 153 targetWorkerNum := f.options.WorkerCount() 154 currentWorkerNum := len(f.workerShutdownCh) 155 156 if targetWorkerNum == currentWorkerNum { 157 continue 158 } 159 160 if targetWorkerNum > currentWorkerNum { 161 f.startWorkers(targetWorkerNum - currentWorkerNum) 162 } else { 163 f.stopWorkers(currentWorkerNum - targetWorkerNum) 164 } 165 f.logger.Info("Update worker pool size", tag.Key("worker-pool-size"), tag.Value(targetWorkerNum)) 166 } 167 } 168 } 169 170 func (f *FIFOScheduler[T]) startWorkers( 171 count int, 172 ) { 173 for i := 0; i < count; i++ { 174 shutdownCh := make(chan struct{}) 175 f.workerShutdownCh = append(f.workerShutdownCh, shutdownCh) 176 177 f.shutdownWG.Add(1) 178 go f.processTask(shutdownCh) 179 } 180 } 181 182 func (f *FIFOScheduler[T]) stopWorkers( 183 count int, 184 ) { 185 shutdownChToClose := f.workerShutdownCh[:count] 186 f.workerShutdownCh = f.workerShutdownCh[count:] 187 188 for _, shutdownCh := range shutdownChToClose { 189 close(shutdownCh) 190 } 191 } 192 193 func (f *FIFOScheduler[T]) processTask( 194 shutdownCh chan struct{}, 195 ) { 196 defer f.shutdownWG.Done() 197 198 for { 199 if f.isStopped() { 200 return 201 } 202 203 select { 204 case <-shutdownCh: 205 return 206 default: 207 } 208 209 select { 210 case task := <-f.tasksChan: 211 f.executeTask(task) 212 213 case <-shutdownCh: 214 return 215 } 216 } 217 } 218 219 func (f *FIFOScheduler[T]) executeTask( 220 task T, 221 ) { 222 operation := func() error { 223 if err := task.Execute(); err != nil { 224 return task.HandleErr(err) 225 } 226 return nil 227 } 228 229 isRetryable := func(err error) bool { 230 return !f.isStopped() && task.IsRetryableError(err) 231 } 232 233 if err := backoff.ThrottleRetry(operation, task.RetryPolicy(), isRetryable); err != nil { 234 if f.isStopped() { 235 task.Abort() 236 return 237 } 238 239 task.Nack(err) 240 return 241 } 242 243 task.Ack() 244 } 245 246 func (f *FIFOScheduler[T]) drainTasks() { 247 LoopDrain: 248 for { 249 select { 250 case task := <-f.tasksChan: 251 task.Abort() 252 default: 253 break LoopDrain 254 } 255 } 256 } 257 258 func (f *FIFOScheduler[T]) isStopped() bool { 259 return atomic.LoadInt32(&f.status) == common.DaemonStatusStopped 260 }