k8s.io/client-go@v0.31.1/util/workqueue/queue.go (about) 1 /* 2 Copyright 2015 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package workqueue 18 19 import ( 20 "sync" 21 "time" 22 23 "k8s.io/utils/clock" 24 ) 25 26 // Deprecated: Interface is deprecated, use TypedInterface instead. 27 type Interface TypedInterface[any] 28 29 type TypedInterface[T comparable] interface { 30 Add(item T) 31 Len() int 32 Get() (item T, shutdown bool) 33 Done(item T) 34 ShutDown() 35 ShutDownWithDrain() 36 ShuttingDown() bool 37 } 38 39 // Queue is the underlying storage for items. The functions below are always 40 // called from the same goroutine. 41 type Queue[T comparable] interface { 42 // Touch can be hooked when an existing item is added again. This may be 43 // useful if the implementation allows priority change for the given item. 44 Touch(item T) 45 // Push adds a new item. 46 Push(item T) 47 // Len tells the total number of items. 48 Len() int 49 // Pop retrieves an item. 50 Pop() (item T) 51 } 52 53 // DefaultQueue is a slice based FIFO queue. 54 func DefaultQueue[T comparable]() Queue[T] { 55 return new(queue[T]) 56 } 57 58 // queue is a slice which implements Queue. 59 type queue[T comparable] []T 60 61 func (q *queue[T]) Touch(item T) {} 62 63 func (q *queue[T]) Push(item T) { 64 *q = append(*q, item) 65 } 66 67 func (q *queue[T]) Len() int { 68 return len(*q) 69 } 70 71 func (q *queue[T]) Pop() (item T) { 72 item = (*q)[0] 73 74 // The underlying array still exists and reference this object, so the object will not be garbage collected. 75 (*q)[0] = *new(T) 76 *q = (*q)[1:] 77 78 return item 79 } 80 81 // QueueConfig specifies optional configurations to customize an Interface. 82 // Deprecated: use TypedQueueConfig instead. 83 type QueueConfig = TypedQueueConfig[any] 84 85 type TypedQueueConfig[T comparable] struct { 86 // Name for the queue. If unnamed, the metrics will not be registered. 87 Name string 88 89 // MetricsProvider optionally allows specifying a metrics provider to use for the queue 90 // instead of the global provider. 91 MetricsProvider MetricsProvider 92 93 // Clock ability to inject real or fake clock for testing purposes. 94 Clock clock.WithTicker 95 96 // Queue provides the underlying queue to use. It is optional and defaults to slice based FIFO queue. 97 Queue Queue[T] 98 } 99 100 // New constructs a new work queue (see the package comment). 101 // 102 // Deprecated: use NewTyped instead. 103 func New() *Type { 104 return NewWithConfig(QueueConfig{ 105 Name: "", 106 }) 107 } 108 109 // NewTyped constructs a new work queue (see the package comment). 110 func NewTyped[T comparable]() *Typed[T] { 111 return NewTypedWithConfig(TypedQueueConfig[T]{ 112 Name: "", 113 }) 114 } 115 116 // NewWithConfig constructs a new workqueue with ability to 117 // customize different properties. 118 // 119 // Deprecated: use NewTypedWithConfig instead. 120 func NewWithConfig(config QueueConfig) *Type { 121 return NewTypedWithConfig(config) 122 } 123 124 // NewTypedWithConfig constructs a new workqueue with ability to 125 // customize different properties. 126 func NewTypedWithConfig[T comparable](config TypedQueueConfig[T]) *Typed[T] { 127 return newQueueWithConfig(config, defaultUnfinishedWorkUpdatePeriod) 128 } 129 130 // NewNamed creates a new named queue. 131 // Deprecated: Use NewWithConfig instead. 132 func NewNamed(name string) *Type { 133 return NewWithConfig(QueueConfig{ 134 Name: name, 135 }) 136 } 137 138 // newQueueWithConfig constructs a new named workqueue 139 // with the ability to customize different properties for testing purposes 140 func newQueueWithConfig[T comparable](config TypedQueueConfig[T], updatePeriod time.Duration) *Typed[T] { 141 var metricsFactory *queueMetricsFactory 142 if config.MetricsProvider != nil { 143 metricsFactory = &queueMetricsFactory{ 144 metricsProvider: config.MetricsProvider, 145 } 146 } else { 147 metricsFactory = &globalMetricsFactory 148 } 149 150 if config.Clock == nil { 151 config.Clock = clock.RealClock{} 152 } 153 154 if config.Queue == nil { 155 config.Queue = DefaultQueue[T]() 156 } 157 158 return newQueue( 159 config.Clock, 160 config.Queue, 161 metricsFactory.newQueueMetrics(config.Name, config.Clock), 162 updatePeriod, 163 ) 164 } 165 166 func newQueue[T comparable](c clock.WithTicker, queue Queue[T], metrics queueMetrics, updatePeriod time.Duration) *Typed[T] { 167 t := &Typed[T]{ 168 clock: c, 169 queue: queue, 170 dirty: set[T]{}, 171 processing: set[T]{}, 172 cond: sync.NewCond(&sync.Mutex{}), 173 metrics: metrics, 174 unfinishedWorkUpdatePeriod: updatePeriod, 175 } 176 177 // Don't start the goroutine for a type of noMetrics so we don't consume 178 // resources unnecessarily 179 if _, ok := metrics.(noMetrics); !ok { 180 go t.updateUnfinishedWorkLoop() 181 } 182 183 return t 184 } 185 186 const defaultUnfinishedWorkUpdatePeriod = 500 * time.Millisecond 187 188 // Type is a work queue (see the package comment). 189 // Deprecated: Use Typed instead. 190 type Type = Typed[any] 191 192 type Typed[t comparable] struct { 193 // queue defines the order in which we will work on items. Every 194 // element of queue should be in the dirty set and not in the 195 // processing set. 196 queue Queue[t] 197 198 // dirty defines all of the items that need to be processed. 199 dirty set[t] 200 201 // Things that are currently being processed are in the processing set. 202 // These things may be simultaneously in the dirty set. When we finish 203 // processing something and remove it from this set, we'll check if 204 // it's in the dirty set, and if so, add it to the queue. 205 processing set[t] 206 207 cond *sync.Cond 208 209 shuttingDown bool 210 drain bool 211 212 metrics queueMetrics 213 214 unfinishedWorkUpdatePeriod time.Duration 215 clock clock.WithTicker 216 } 217 218 type empty struct{} 219 type t interface{} 220 type set[t comparable] map[t]empty 221 222 func (s set[t]) has(item t) bool { 223 _, exists := s[item] 224 return exists 225 } 226 227 func (s set[t]) insert(item t) { 228 s[item] = empty{} 229 } 230 231 func (s set[t]) delete(item t) { 232 delete(s, item) 233 } 234 235 func (s set[t]) len() int { 236 return len(s) 237 } 238 239 // Add marks item as needing processing. 240 func (q *Typed[T]) Add(item T) { 241 q.cond.L.Lock() 242 defer q.cond.L.Unlock() 243 if q.shuttingDown { 244 return 245 } 246 if q.dirty.has(item) { 247 // the same item is added again before it is processed, call the Touch 248 // function if the queue cares about it (for e.g, reset its priority) 249 if !q.processing.has(item) { 250 q.queue.Touch(item) 251 } 252 return 253 } 254 255 q.metrics.add(item) 256 257 q.dirty.insert(item) 258 if q.processing.has(item) { 259 return 260 } 261 262 q.queue.Push(item) 263 q.cond.Signal() 264 } 265 266 // Len returns the current queue length, for informational purposes only. You 267 // shouldn't e.g. gate a call to Add() or Get() on Len() being a particular 268 // value, that can't be synchronized properly. 269 func (q *Typed[T]) Len() int { 270 q.cond.L.Lock() 271 defer q.cond.L.Unlock() 272 return q.queue.Len() 273 } 274 275 // Get blocks until it can return an item to be processed. If shutdown = true, 276 // the caller should end their goroutine. You must call Done with item when you 277 // have finished processing it. 278 func (q *Typed[T]) Get() (item T, shutdown bool) { 279 q.cond.L.Lock() 280 defer q.cond.L.Unlock() 281 for q.queue.Len() == 0 && !q.shuttingDown { 282 q.cond.Wait() 283 } 284 if q.queue.Len() == 0 { 285 // We must be shutting down. 286 return *new(T), true 287 } 288 289 item = q.queue.Pop() 290 291 q.metrics.get(item) 292 293 q.processing.insert(item) 294 q.dirty.delete(item) 295 296 return item, false 297 } 298 299 // Done marks item as done processing, and if it has been marked as dirty again 300 // while it was being processed, it will be re-added to the queue for 301 // re-processing. 302 func (q *Typed[T]) Done(item T) { 303 q.cond.L.Lock() 304 defer q.cond.L.Unlock() 305 306 q.metrics.done(item) 307 308 q.processing.delete(item) 309 if q.dirty.has(item) { 310 q.queue.Push(item) 311 q.cond.Signal() 312 } else if q.processing.len() == 0 { 313 q.cond.Signal() 314 } 315 } 316 317 // ShutDown will cause q to ignore all new items added to it and 318 // immediately instruct the worker goroutines to exit. 319 func (q *Typed[T]) ShutDown() { 320 q.cond.L.Lock() 321 defer q.cond.L.Unlock() 322 323 q.drain = false 324 q.shuttingDown = true 325 q.cond.Broadcast() 326 } 327 328 // ShutDownWithDrain will cause q to ignore all new items added to it. As soon 329 // as the worker goroutines have "drained", i.e: finished processing and called 330 // Done on all existing items in the queue; they will be instructed to exit and 331 // ShutDownWithDrain will return. Hence: a strict requirement for using this is; 332 // your workers must ensure that Done is called on all items in the queue once 333 // the shut down has been initiated, if that is not the case: this will block 334 // indefinitely. It is, however, safe to call ShutDown after having called 335 // ShutDownWithDrain, as to force the queue shut down to terminate immediately 336 // without waiting for the drainage. 337 func (q *Typed[T]) ShutDownWithDrain() { 338 q.cond.L.Lock() 339 defer q.cond.L.Unlock() 340 341 q.drain = true 342 q.shuttingDown = true 343 q.cond.Broadcast() 344 345 for q.processing.len() != 0 && q.drain { 346 q.cond.Wait() 347 } 348 } 349 350 func (q *Typed[T]) ShuttingDown() bool { 351 q.cond.L.Lock() 352 defer q.cond.L.Unlock() 353 354 return q.shuttingDown 355 } 356 357 func (q *Typed[T]) updateUnfinishedWorkLoop() { 358 t := q.clock.NewTicker(q.unfinishedWorkUpdatePeriod) 359 defer t.Stop() 360 for range t.C() { 361 if !func() bool { 362 q.cond.L.Lock() 363 defer q.cond.L.Unlock() 364 if !q.shuttingDown { 365 q.metrics.updateUnfinishedWork() 366 return true 367 } 368 return false 369 370 }() { 371 return 372 } 373 } 374 }