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