k8s.io/client-go@v0.31.1/util/workqueue/delaying_queue.go (about) 1 /* 2 Copyright 2016 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 "container/heap" 21 "sync" 22 "time" 23 24 utilruntime "k8s.io/apimachinery/pkg/util/runtime" 25 "k8s.io/utils/clock" 26 ) 27 28 // DelayingInterface is an Interface that can Add an item at a later time. This makes it easier to 29 // requeue items after failures without ending up in a hot-loop. 30 // 31 // Deprecated: use TypedDelayingInterface instead. 32 type DelayingInterface TypedDelayingInterface[any] 33 34 // TypedDelayingInterface is an Interface that can Add an item at a later time. This makes it easier to 35 // requeue items after failures without ending up in a hot-loop. 36 type TypedDelayingInterface[T comparable] interface { 37 TypedInterface[T] 38 // AddAfter adds an item to the workqueue after the indicated duration has passed 39 AddAfter(item T, duration time.Duration) 40 } 41 42 // DelayingQueueConfig specifies optional configurations to customize a DelayingInterface. 43 // 44 // Deprecated: use TypedDelayingQueueConfig instead. 45 type DelayingQueueConfig = TypedDelayingQueueConfig[any] 46 47 // TypedDelayingQueueConfig specifies optional configurations to customize a DelayingInterface. 48 type TypedDelayingQueueConfig[T comparable] struct { 49 // Name for the queue. If unnamed, the metrics will not be registered. 50 Name string 51 52 // MetricsProvider optionally allows specifying a metrics provider to use for the queue 53 // instead of the global provider. 54 MetricsProvider MetricsProvider 55 56 // Clock optionally allows injecting a real or fake clock for testing purposes. 57 Clock clock.WithTicker 58 59 // Queue optionally allows injecting custom queue Interface instead of the default one. 60 Queue TypedInterface[T] 61 } 62 63 // NewDelayingQueue constructs a new workqueue with delayed queuing ability. 64 // NewDelayingQueue does not emit metrics. For use with a MetricsProvider, please use 65 // NewDelayingQueueWithConfig instead and specify a name. 66 // 67 // Deprecated: use TypedNewDelayingQueue instead. 68 func NewDelayingQueue() DelayingInterface { 69 return NewDelayingQueueWithConfig(DelayingQueueConfig{}) 70 } 71 72 // TypedNewDelayingQueue constructs a new workqueue with delayed queuing ability. 73 // TypedNewDelayingQueue does not emit metrics. For use with a MetricsProvider, please use 74 // TypedNewDelayingQueueWithConfig instead and specify a name. 75 func TypedNewDelayingQueue[T comparable]() TypedDelayingInterface[T] { 76 return NewTypedDelayingQueueWithConfig(TypedDelayingQueueConfig[T]{}) 77 } 78 79 // NewDelayingQueueWithConfig constructs a new workqueue with options to 80 // customize different properties. 81 // 82 // Deprecated: use TypedNewDelayingQueueWithConfig instead. 83 func NewDelayingQueueWithConfig(config DelayingQueueConfig) DelayingInterface { 84 return NewTypedDelayingQueueWithConfig[any](config) 85 } 86 87 // NewTypedDelayingQueueWithConfig constructs a new workqueue with options to 88 // customize different properties. 89 func NewTypedDelayingQueueWithConfig[T comparable](config TypedDelayingQueueConfig[T]) TypedDelayingInterface[T] { 90 if config.Clock == nil { 91 config.Clock = clock.RealClock{} 92 } 93 94 if config.Queue == nil { 95 config.Queue = NewTypedWithConfig[T](TypedQueueConfig[T]{ 96 Name: config.Name, 97 MetricsProvider: config.MetricsProvider, 98 Clock: config.Clock, 99 }) 100 } 101 102 return newDelayingQueue(config.Clock, config.Queue, config.Name, config.MetricsProvider) 103 } 104 105 // NewDelayingQueueWithCustomQueue constructs a new workqueue with ability to 106 // inject custom queue Interface instead of the default one 107 // Deprecated: Use NewDelayingQueueWithConfig instead. 108 func NewDelayingQueueWithCustomQueue(q Interface, name string) DelayingInterface { 109 return NewDelayingQueueWithConfig(DelayingQueueConfig{ 110 Name: name, 111 Queue: q, 112 }) 113 } 114 115 // NewNamedDelayingQueue constructs a new named workqueue with delayed queuing ability. 116 // Deprecated: Use NewDelayingQueueWithConfig instead. 117 func NewNamedDelayingQueue(name string) DelayingInterface { 118 return NewDelayingQueueWithConfig(DelayingQueueConfig{Name: name}) 119 } 120 121 // NewDelayingQueueWithCustomClock constructs a new named workqueue 122 // with ability to inject real or fake clock for testing purposes. 123 // Deprecated: Use NewDelayingQueueWithConfig instead. 124 func NewDelayingQueueWithCustomClock(clock clock.WithTicker, name string) DelayingInterface { 125 return NewDelayingQueueWithConfig(DelayingQueueConfig{ 126 Name: name, 127 Clock: clock, 128 }) 129 } 130 131 func newDelayingQueue[T comparable](clock clock.WithTicker, q TypedInterface[T], name string, provider MetricsProvider) *delayingType[T] { 132 ret := &delayingType[T]{ 133 TypedInterface: q, 134 clock: clock, 135 heartbeat: clock.NewTicker(maxWait), 136 stopCh: make(chan struct{}), 137 waitingForAddCh: make(chan *waitFor, 1000), 138 metrics: newRetryMetrics(name, provider), 139 } 140 141 go ret.waitingLoop() 142 return ret 143 } 144 145 // delayingType wraps an Interface and provides delayed re-enquing 146 type delayingType[T comparable] struct { 147 TypedInterface[T] 148 149 // clock tracks time for delayed firing 150 clock clock.Clock 151 152 // stopCh lets us signal a shutdown to the waiting loop 153 stopCh chan struct{} 154 // stopOnce guarantees we only signal shutdown a single time 155 stopOnce sync.Once 156 157 // heartbeat ensures we wait no more than maxWait before firing 158 heartbeat clock.Ticker 159 160 // waitingForAddCh is a buffered channel that feeds waitingForAdd 161 waitingForAddCh chan *waitFor 162 163 // metrics counts the number of retries 164 metrics retryMetrics 165 } 166 167 // waitFor holds the data to add and the time it should be added 168 type waitFor struct { 169 data t 170 readyAt time.Time 171 // index in the priority queue (heap) 172 index int 173 } 174 175 // waitForPriorityQueue implements a priority queue for waitFor items. 176 // 177 // waitForPriorityQueue implements heap.Interface. The item occurring next in 178 // time (i.e., the item with the smallest readyAt) is at the root (index 0). 179 // Peek returns this minimum item at index 0. Pop returns the minimum item after 180 // it has been removed from the queue and placed at index Len()-1 by 181 // container/heap. Push adds an item at index Len(), and container/heap 182 // percolates it into the correct location. 183 type waitForPriorityQueue []*waitFor 184 185 func (pq waitForPriorityQueue) Len() int { 186 return len(pq) 187 } 188 func (pq waitForPriorityQueue) Less(i, j int) bool { 189 return pq[i].readyAt.Before(pq[j].readyAt) 190 } 191 func (pq waitForPriorityQueue) Swap(i, j int) { 192 pq[i], pq[j] = pq[j], pq[i] 193 pq[i].index = i 194 pq[j].index = j 195 } 196 197 // Push adds an item to the queue. Push should not be called directly; instead, 198 // use `heap.Push`. 199 func (pq *waitForPriorityQueue) Push(x interface{}) { 200 n := len(*pq) 201 item := x.(*waitFor) 202 item.index = n 203 *pq = append(*pq, item) 204 } 205 206 // Pop removes an item from the queue. Pop should not be called directly; 207 // instead, use `heap.Pop`. 208 func (pq *waitForPriorityQueue) Pop() interface{} { 209 n := len(*pq) 210 item := (*pq)[n-1] 211 item.index = -1 212 *pq = (*pq)[0:(n - 1)] 213 return item 214 } 215 216 // Peek returns the item at the beginning of the queue, without removing the 217 // item or otherwise mutating the queue. It is safe to call directly. 218 func (pq waitForPriorityQueue) Peek() interface{} { 219 return pq[0] 220 } 221 222 // ShutDown stops the queue. After the queue drains, the returned shutdown bool 223 // on Get() will be true. This method may be invoked more than once. 224 func (q *delayingType[T]) ShutDown() { 225 q.stopOnce.Do(func() { 226 q.TypedInterface.ShutDown() 227 close(q.stopCh) 228 q.heartbeat.Stop() 229 }) 230 } 231 232 // AddAfter adds the given item to the work queue after the given delay 233 func (q *delayingType[T]) AddAfter(item T, duration time.Duration) { 234 // don't add if we're already shutting down 235 if q.ShuttingDown() { 236 return 237 } 238 239 q.metrics.retry() 240 241 // immediately add things with no delay 242 if duration <= 0 { 243 q.Add(item) 244 return 245 } 246 247 select { 248 case <-q.stopCh: 249 // unblock if ShutDown() is called 250 case q.waitingForAddCh <- &waitFor{data: item, readyAt: q.clock.Now().Add(duration)}: 251 } 252 } 253 254 // maxWait keeps a max bound on the wait time. It's just insurance against weird things happening. 255 // Checking the queue every 10 seconds isn't expensive and we know that we'll never end up with an 256 // expired item sitting for more than 10 seconds. 257 const maxWait = 10 * time.Second 258 259 // waitingLoop runs until the workqueue is shutdown and keeps a check on the list of items to be added. 260 func (q *delayingType[T]) waitingLoop() { 261 defer utilruntime.HandleCrash() 262 263 // Make a placeholder channel to use when there are no items in our list 264 never := make(<-chan time.Time) 265 266 // Make a timer that expires when the item at the head of the waiting queue is ready 267 var nextReadyAtTimer clock.Timer 268 269 waitingForQueue := &waitForPriorityQueue{} 270 heap.Init(waitingForQueue) 271 272 waitingEntryByData := map[t]*waitFor{} 273 274 for { 275 if q.TypedInterface.ShuttingDown() { 276 return 277 } 278 279 now := q.clock.Now() 280 281 // Add ready entries 282 for waitingForQueue.Len() > 0 { 283 entry := waitingForQueue.Peek().(*waitFor) 284 if entry.readyAt.After(now) { 285 break 286 } 287 288 entry = heap.Pop(waitingForQueue).(*waitFor) 289 q.Add(entry.data.(T)) 290 delete(waitingEntryByData, entry.data) 291 } 292 293 // Set up a wait for the first item's readyAt (if one exists) 294 nextReadyAt := never 295 if waitingForQueue.Len() > 0 { 296 if nextReadyAtTimer != nil { 297 nextReadyAtTimer.Stop() 298 } 299 entry := waitingForQueue.Peek().(*waitFor) 300 nextReadyAtTimer = q.clock.NewTimer(entry.readyAt.Sub(now)) 301 nextReadyAt = nextReadyAtTimer.C() 302 } 303 304 select { 305 case <-q.stopCh: 306 return 307 308 case <-q.heartbeat.C(): 309 // continue the loop, which will add ready items 310 311 case <-nextReadyAt: 312 // continue the loop, which will add ready items 313 314 case waitEntry := <-q.waitingForAddCh: 315 if waitEntry.readyAt.After(q.clock.Now()) { 316 insert(waitingForQueue, waitingEntryByData, waitEntry) 317 } else { 318 q.Add(waitEntry.data.(T)) 319 } 320 321 drained := false 322 for !drained { 323 select { 324 case waitEntry := <-q.waitingForAddCh: 325 if waitEntry.readyAt.After(q.clock.Now()) { 326 insert(waitingForQueue, waitingEntryByData, waitEntry) 327 } else { 328 q.Add(waitEntry.data.(T)) 329 } 330 default: 331 drained = true 332 } 333 } 334 } 335 } 336 } 337 338 // insert adds the entry to the priority queue, or updates the readyAt if it already exists in the queue 339 func insert(q *waitForPriorityQueue, knownEntries map[t]*waitFor, entry *waitFor) { 340 // if the entry already exists, update the time only if it would cause the item to be queued sooner 341 existing, exists := knownEntries[entry.data] 342 if exists { 343 if existing.readyAt.After(entry.readyAt) { 344 existing.readyAt = entry.readyAt 345 heap.Fix(q, existing.index) 346 } 347 348 return 349 } 350 351 heap.Push(q, entry) 352 knownEntries[entry.data] = entry 353 }