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 }