knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/controller/two_lane_queue_test.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  	"context"
    21  	"strconv"
    22  	"testing"
    23  	"time"
    24  
    25  	"k8s.io/apimachinery/pkg/util/wait"
    26  	"k8s.io/client-go/util/workqueue"
    27  )
    28  
    29  type chanRateLimiter struct {
    30  	t *testing.T
    31  	// Called when this ratelimiter is consulted for when to process a value.
    32  	whenCalled chan interface{}
    33  }
    34  
    35  func (r *chanRateLimiter) When(item interface{}) time.Duration {
    36  	r.whenCalled <- item
    37  	return 0
    38  }
    39  
    40  func (r *chanRateLimiter) Forget(item interface{}) {
    41  	r.t.Fatalf("Forgetting item %+v, we should not be forgetting any items.", item)
    42  }
    43  
    44  func (r *chanRateLimiter) NumRequeues(item interface{}) int {
    45  	return 0
    46  }
    47  
    48  var _ workqueue.TypedRateLimiter[any] = &chanRateLimiter{}
    49  
    50  func TestRateLimit(t *testing.T) {
    51  	// Verifies that we properly pass the rate limiter to the queue.
    52  	rl := &chanRateLimiter{
    53  		t:          t,
    54  		whenCalled: make(chan interface{}, 1),
    55  	}
    56  
    57  	q := newTwoLaneWorkQueue("live-in-the-limited-lane", rl)
    58  
    59  	// Verify the fast lane has the proper RL.
    60  	q.AddRateLimited("2")
    61  	select {
    62  	case <-rl.whenCalled:
    63  		// As desired.
    64  	default:
    65  		t.Error("Didn't go to the proper rate limiter.")
    66  	}
    67  
    68  	// Verify the items were properly added for consumption.
    69  	if wait.PollUntilContextTimeout(context.Background(), 10*time.Millisecond, 250*time.Millisecond, true, func(ctx context.Context) (bool, error) {
    70  		return q.Len() == 1, nil
    71  	}) != nil {
    72  		t.Error("Queue length was never 1")
    73  	}
    74  	// And drain.
    75  	q.ShutDown()
    76  	for q.Len() > 0 {
    77  		q.Get()
    78  	}
    79  }
    80  
    81  func TestSlowQueue(t *testing.T) {
    82  	q := newTwoLaneWorkQueue("live-in-the-fast-lane", workqueue.DefaultTypedControllerRateLimiter[any]())
    83  	q.AddSlow("1")
    84  	// Queue has async moving parts so if we check at the wrong moment, this might still be 0.
    85  	if wait.PollUntilContextTimeout(context.Background(), 10*time.Millisecond, 250*time.Millisecond, true, func(ctx context.Context) (bool, error) {
    86  		return q.Len() == 1, nil
    87  	}) != nil {
    88  		t.Error("Queue length was never 1")
    89  	}
    90  
    91  	k, done := q.Get()
    92  	if got, want := k.(string), "1"; got != want {
    93  		t.Errorf(`Got = %q, want: "1"`, got)
    94  	}
    95  	if done {
    96  		t.Error("The queue is unexpectedly shutdown")
    97  	}
    98  	q.Done(k)
    99  	q.ShutDown()
   100  	if !q.slowLane().ShuttingDown() {
   101  		t.Error("ShutDown did not propagate to the slow queue")
   102  	}
   103  	if _, done := q.Get(); !done {
   104  		t.Error("Get did not return positive shutdown signal")
   105  	}
   106  }
   107  
   108  func TestDoubleKey(t *testing.T) {
   109  	// Verifies that we don't get double concurrent processing of the same key.
   110  	q := newTwoLaneWorkQueue("live-in-the-fast-lane", workqueue.DefaultTypedControllerRateLimiter[any]())
   111  	q.Add("1")
   112  	t.Cleanup(q.ShutDown)
   113  
   114  	k, done := q.Get()
   115  	if got, want := k.(string), "1"; got != want {
   116  		t.Errorf(`Got = %q, want: "1"`, got)
   117  	}
   118  	if done {
   119  		t.Error("The queue is unexpectedly shutdown")
   120  	}
   121  
   122  	// This should not be read from the queue until we actually call `Done`.
   123  	q.AddSlow("1")
   124  	sentinel := make(chan struct{})
   125  	go func() {
   126  		defer close(sentinel)
   127  		k, done := q.Get()
   128  		if got, want := k.(string), "1"; got != want {
   129  			t.Errorf(`2nd time got = %q, want: "1"`, got)
   130  		}
   131  		if done {
   132  			t.Error("The queue is unexpectedly shutdown")
   133  		}
   134  		q.Done(k)
   135  	}()
   136  	select {
   137  	case <-sentinel:
   138  		t.Error("The sentinel should not have fired")
   139  	case <-time.After(600 * time.Millisecond):
   140  		// Expected.
   141  	}
   142  	// This should permit the re-reading of the same key.
   143  	q.Done(k)
   144  	select {
   145  	case <-sentinel:
   146  		// Expected.
   147  	case <-time.After(200 * time.Millisecond):
   148  		t.Error("The item was not processed as expected")
   149  	}
   150  }
   151  
   152  func TestOrder(t *testing.T) {
   153  	// Verifies that we read from the fast queue first.
   154  	q := newTwoLaneWorkQueue("live-in-the-fast-lane", workqueue.DefaultTypedControllerRateLimiter[any]())
   155  	stop := make(chan struct{})
   156  	t.Cleanup(func() {
   157  		close(stop)
   158  		q.ShutDown()
   159  		// Drain the rest.
   160  		for q.Len() > 0 {
   161  			q.Get()
   162  		}
   163  	})
   164  
   165  	go func() {
   166  		for i := 1; ; i++ {
   167  			q.Add(strconv.Itoa(i))
   168  			// Get fewer of those, to ensure the first priority select wins.
   169  			if i%2 == 0 {
   170  				q.AddSlow("slow" + strconv.Itoa(i))
   171  			}
   172  			select {
   173  			case <-stop:
   174  				return
   175  			default:
   176  			}
   177  		}
   178  	}()
   179  	done := time.After(300 * time.Millisecond)
   180  	for {
   181  		select {
   182  		case <-done:
   183  			return
   184  		default:
   185  		}
   186  		v, sd := q.Get()
   187  		if sd {
   188  			t.Error("Got shutdown signal")
   189  		} else if v.(string) == "slow" {
   190  			t.Error("Got item from the slow queue")
   191  		}
   192  		q.Done(v)
   193  	}
   194  }