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  }