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  }