knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/controller/two_lane_queue.go (about) 1 /* 2 Copyright 2020 The Knative 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 controller 18 19 import ( 20 "time" 21 22 "k8s.io/client-go/util/workqueue" 23 "k8s.io/utils/clock" 24 ) 25 26 // twoLaneQueue is a rate limited queue that wraps around two queues 27 // -- fast queue (anonymously aliased), whose contents are processed with priority. 28 // -- slow queue (slowLane queue), whose contents are processed if fast queue has no items. 29 // All the default methods operate on the fast queue, unless noted otherwise. 30 type twoLaneQueue struct { 31 fastLane workqueue.TypedInterface[any] 32 slowLane workqueue.TypedInterface[any] 33 // consumerQueue is necessary to ensure that we're not reconciling 34 // the same object at the exact same time (e.g. if it had been enqueued 35 // in both fast and slow and is the only object there). 36 consumerQueue workqueue.TypedInterface[any] 37 38 name string 39 40 fastChan chan any 41 slowChan chan any 42 43 metrics *queueMetrics 44 } 45 46 type twoLaneRateLimitingQueue struct { 47 q *twoLaneQueue 48 workqueue.TypedRateLimitingInterface[any] 49 } 50 51 var _ workqueue.TypedInterface[any] = (*twoLaneQueue)(nil) 52 53 // Creates a new twoLaneQueue. 54 func newTwoLaneWorkQueue(name string, rl workqueue.TypedRateLimiter[any]) *twoLaneRateLimitingQueue { 55 mp := globalMetricsProvider 56 57 tlq := &twoLaneQueue{ 58 name: name, 59 fastLane: workqueue.NewTyped[any](), 60 slowLane: workqueue.NewTyped[any](), 61 consumerQueue: workqueue.NewTyped[any](), 62 fastChan: make(chan any), 63 slowChan: make(chan any), 64 } 65 66 tlq.metrics = createMetrics(tlq, mp, name) 67 68 // Run consumer thread. 69 go tlq.runConsumer() 70 // Run producer threads. 71 go process(tlq.fastLane, tlq.fastChan) 72 go process(tlq.slowLane, tlq.slowChan) 73 74 q := &twoLaneRateLimitingQueue{ 75 q: tlq, 76 TypedRateLimitingInterface: workqueue.NewTypedRateLimitingQueueWithConfig( 77 rl, 78 workqueue.TypedRateLimitingQueueConfig[any]{ 79 DelayingQueue: workqueue.NewTypedDelayingQueueWithConfig( 80 workqueue.TypedDelayingQueueConfig[any]{ 81 Name: name, // Name needs to be set for retry metrics 82 Queue: tlq, 83 MetricsProvider: mp, 84 }, 85 ), 86 }, 87 ), 88 } 89 return q 90 } 91 92 func createMetrics(q *twoLaneQueue, mp workqueue.MetricsProvider, name string) *queueMetrics { 93 if mp == noopProvider { 94 return nil 95 } 96 97 m := &queueMetrics{ 98 clock: clock.RealClock{}, 99 depth: mp.NewDepthMetric(name), 100 adds: mp.NewAddsMetric(name), 101 latency: mp.NewLatencyMetric(name), 102 workDuration: mp.NewWorkDurationMetric(name), 103 unfinishedWorkSeconds: mp.NewUnfinishedWorkSecondsMetric(name), 104 longestRunningProcessor: mp.NewUnfinishedWorkSecondsMetric(name), 105 addTimes: make(map[any]time.Time), 106 processingStartTimes: make(map[any]time.Time), 107 } 108 109 go updateUnfinishedWorkLoop(q) 110 111 return m 112 } 113 114 func updateUnfinishedWorkLoop(q *twoLaneQueue) { 115 t := time.NewTicker(time.Second) 116 defer t.Stop() 117 118 for range t.C { 119 if q.ShuttingDown() { 120 return 121 } 122 123 q.metrics.updateUnfinishedWork() 124 } 125 } 126 127 func process(q workqueue.TypedInterface[any], ch chan any) { 128 // Sender closes the channel 129 defer close(ch) 130 for { 131 i, d := q.Get() 132 // If the queue is empty and we're shutting down — stop the loop. 133 if d { 134 break 135 } 136 q.Done(i) 137 ch <- i 138 } 139 } 140 141 func (tlq *twoLaneQueue) runConsumer() { 142 // Shutdown flags. 143 fast, slow := true, true 144 // When both producer queues are shutdown stop the consumerQueue. 145 defer tlq.consumerQueue.ShutDown() 146 // While any of the queues is still running, try to read off of them. 147 for fast || slow { 148 // By default drain the fast lane. 149 // Channels in select are picked random, so first 150 // we have a select that only looks at the fast lane queue. 151 if fast { 152 select { 153 case item, ok := <-tlq.fastChan: 154 if !ok { 155 // This queue is shutdown and drained. Stop looking at it. 156 fast = false 157 continue 158 } 159 tlq.consumerQueue.Add(item) 160 continue 161 default: 162 // This immediately exits the wait if the fast chan is empty. 163 } 164 } 165 166 // If the fast lane queue had no items, we can select from both. 167 // Obviously if suddenly both are populated at the same time there's a 168 // 50% chance that the slow would be picked first, but this should be 169 // a rare occasion not to really worry about it. 170 select { 171 case item, ok := <-tlq.fastChan: 172 if !ok { 173 // This queue is shutdown and drained. Stop looking at it. 174 fast = false 175 continue 176 } 177 tlq.consumerQueue.Add(item) 178 case item, ok := <-tlq.slowChan: 179 if !ok { 180 // This queue is shutdown and drained. Stop looking at it. 181 slow = false 182 continue 183 } 184 tlq.consumerQueue.Add(item) 185 } 186 } 187 } 188 189 // Shutdown implements workqueue.Interface. 190 // Shutdown shuts down both queues. 191 func (tlq *twoLaneQueue) ShutDown() { 192 tlq.fastLane.ShutDown() 193 tlq.slowLane.ShutDown() 194 } 195 196 // Done implements workqueue.Interface. 197 // Done marks the item as completed in all the queues. 198 // NB: this will just re-enqueue the object on the queue that didn't originate the object. 199 func (tlq *twoLaneQueue) Done(item any) { 200 tlq.consumerQueue.Done(item) 201 tlq.metrics.done(item) 202 } 203 204 func (tlq *twoLaneQueue) Add(item any) { 205 tlq.metrics.add(item) 206 tlq.fastLane.Add(item) 207 } 208 209 func (q *twoLaneRateLimitingQueue) AddSlow(item any) { 210 q.q.metrics.add(item) 211 q.q.slowLane.Add(item) 212 } 213 214 func (q *twoLaneRateLimitingQueue) SlowLen() int { 215 return q.q.slowLane.Len() 216 } 217 218 func (q *twoLaneRateLimitingQueue) slowLane() workqueue.TypedInterface[any] { 219 return q.q.slowLane 220 } 221 222 // Get implements workqueue.Interface. 223 // It gets the item from fast lane if it has anything, alternatively 224 // the slow lane. 225 func (tlq *twoLaneQueue) Get() (any, bool) { 226 item, shutdown := tlq.consumerQueue.Get() 227 tlq.metrics.get(item) 228 return item, shutdown 229 } 230 231 // Len returns the sum of lengths. 232 // NB: actual _number_ of unique object might be less than this sum. 233 func (tlq *twoLaneQueue) Len() int { 234 return tlq.fastLane.Len() + tlq.slowLane.Len() + tlq.consumerQueue.Len() 235 } 236 237 func (tlq *twoLaneQueue) ShutDownWithDrain() { 238 tlq.fastLane.ShutDownWithDrain() 239 tlq.slowLane.ShutDownWithDrain() 240 } 241 242 func (tlq *twoLaneQueue) ShuttingDown() bool { 243 return tlq.fastLane.ShuttingDown() || tlq.slowLane.ShuttingDown() 244 }