k8s.io/client-go@v0.31.1/util/workqueue/queue_test.go (about) 1 /* 2 Copyright 2015 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_test 18 19 import ( 20 "runtime" 21 "sync" 22 "sync/atomic" 23 "testing" 24 "time" 25 26 "k8s.io/apimachinery/pkg/util/wait" 27 "k8s.io/client-go/util/workqueue" 28 ) 29 30 // traceQueue traces whether items are touched 31 type traceQueue struct { 32 workqueue.Queue[any] 33 34 touched map[interface{}]struct{} 35 } 36 37 func (t *traceQueue) Touch(item interface{}) { 38 t.Queue.Touch(item) 39 if t.touched == nil { 40 t.touched = make(map[interface{}]struct{}) 41 } 42 t.touched[item] = struct{}{} 43 } 44 45 var _ workqueue.Queue[any] = &traceQueue{} 46 47 func TestBasic(t *testing.T) { 48 tests := []struct { 49 queue *workqueue.Type 50 queueShutDown func(workqueue.Interface) 51 }{ 52 { 53 queue: workqueue.New(), 54 queueShutDown: workqueue.Interface.ShutDown, 55 }, 56 { 57 queue: workqueue.New(), 58 queueShutDown: workqueue.Interface.ShutDownWithDrain, 59 }, 60 } 61 for _, test := range tests { 62 // If something is seriously wrong this test will never complete. 63 64 // Start producers 65 const producers = 50 66 producerWG := sync.WaitGroup{} 67 producerWG.Add(producers) 68 for i := 0; i < producers; i++ { 69 go func(i int) { 70 defer producerWG.Done() 71 for j := 0; j < 50; j++ { 72 test.queue.Add(i) 73 time.Sleep(time.Millisecond) 74 } 75 }(i) 76 } 77 78 // Start consumers 79 const consumers = 10 80 consumerWG := sync.WaitGroup{} 81 consumerWG.Add(consumers) 82 for i := 0; i < consumers; i++ { 83 go func(i int) { 84 defer consumerWG.Done() 85 for { 86 item, quit := test.queue.Get() 87 if item == "added after shutdown!" { 88 t.Errorf("Got an item added after shutdown.") 89 } 90 if quit { 91 return 92 } 93 t.Logf("Worker %v: begin processing %v", i, item) 94 time.Sleep(3 * time.Millisecond) 95 t.Logf("Worker %v: done processing %v", i, item) 96 test.queue.Done(item) 97 } 98 }(i) 99 } 100 101 producerWG.Wait() 102 test.queueShutDown(test.queue) 103 test.queue.Add("added after shutdown!") 104 consumerWG.Wait() 105 if test.queue.Len() != 0 { 106 t.Errorf("Expected the queue to be empty, had: %v items", test.queue.Len()) 107 } 108 } 109 } 110 111 func TestAddWhileProcessing(t *testing.T) { 112 tests := []struct { 113 queue *workqueue.Type 114 queueShutDown func(workqueue.Interface) 115 }{ 116 { 117 queue: workqueue.New(), 118 queueShutDown: workqueue.Interface.ShutDown, 119 }, 120 { 121 queue: workqueue.New(), 122 queueShutDown: workqueue.Interface.ShutDownWithDrain, 123 }, 124 } 125 for _, test := range tests { 126 127 // Start producers 128 const producers = 50 129 producerWG := sync.WaitGroup{} 130 producerWG.Add(producers) 131 for i := 0; i < producers; i++ { 132 go func(i int) { 133 defer producerWG.Done() 134 test.queue.Add(i) 135 }(i) 136 } 137 138 // Start consumers 139 const consumers = 10 140 consumerWG := sync.WaitGroup{} 141 consumerWG.Add(consumers) 142 for i := 0; i < consumers; i++ { 143 go func(i int) { 144 defer consumerWG.Done() 145 // Every worker will re-add every item up to two times. 146 // This tests the dirty-while-processing case. 147 counters := map[interface{}]int{} 148 for { 149 item, quit := test.queue.Get() 150 if quit { 151 return 152 } 153 counters[item]++ 154 if counters[item] < 2 { 155 test.queue.Add(item) 156 } 157 test.queue.Done(item) 158 } 159 }(i) 160 } 161 162 producerWG.Wait() 163 test.queueShutDown(test.queue) 164 consumerWG.Wait() 165 if test.queue.Len() != 0 { 166 t.Errorf("Expected the queue to be empty, had: %v items", test.queue.Len()) 167 } 168 } 169 } 170 171 func TestLen(t *testing.T) { 172 q := workqueue.New() 173 q.Add("foo") 174 if e, a := 1, q.Len(); e != a { 175 t.Errorf("Expected %v, got %v", e, a) 176 } 177 q.Add("bar") 178 if e, a := 2, q.Len(); e != a { 179 t.Errorf("Expected %v, got %v", e, a) 180 } 181 q.Add("foo") // should not increase the queue length. 182 if e, a := 2, q.Len(); e != a { 183 t.Errorf("Expected %v, got %v", e, a) 184 } 185 } 186 187 func TestReinsert(t *testing.T) { 188 q := workqueue.New() 189 q.Add("foo") 190 191 // Start processing 192 i, _ := q.Get() 193 if i != "foo" { 194 t.Errorf("Expected %v, got %v", "foo", i) 195 } 196 197 // Add it back while processing 198 q.Add(i) 199 200 // Finish it up 201 q.Done(i) 202 203 // It should be back on the queue 204 i, _ = q.Get() 205 if i != "foo" { 206 t.Errorf("Expected %v, got %v", "foo", i) 207 } 208 209 // Finish that one up 210 q.Done(i) 211 212 if a := q.Len(); a != 0 { 213 t.Errorf("Expected queue to be empty. Has %v items", a) 214 } 215 } 216 217 func TestCollapse(t *testing.T) { 218 tq := &traceQueue{Queue: workqueue.DefaultQueue[any]()} 219 q := workqueue.NewWithConfig(workqueue.QueueConfig{ 220 Name: "", 221 Queue: tq, 222 }) 223 // Add a new one twice 224 q.Add("bar") 225 q.Add("bar") 226 227 // It should get the new one 228 i, _ := q.Get() 229 if i != "bar" { 230 t.Errorf("Expected %v, got %v", "bar", i) 231 } 232 233 // Finish that one up 234 q.Done(i) 235 236 // There should be no more objects in the queue 237 if a := q.Len(); a != 0 { 238 t.Errorf("Expected queue to be empty. Has %v items", a) 239 } 240 241 if _, ok := tq.touched["bar"]; !ok { 242 t.Errorf("Expected bar to be Touched") 243 } 244 } 245 246 func TestCollapseWhileProcessing(t *testing.T) { 247 tq := &traceQueue{Queue: workqueue.DefaultQueue[any]()} 248 q := workqueue.NewWithConfig(workqueue.QueueConfig{ 249 Name: "", 250 Queue: tq, 251 }) 252 q.Add("foo") 253 254 // Start processing 255 i, _ := q.Get() 256 if i != "foo" { 257 t.Errorf("Expected %v, got %v", "foo", i) 258 } 259 260 // Add the same one twice 261 q.Add("foo") 262 q.Add("foo") 263 264 waitCh := make(chan struct{}) 265 // simulate another worker consuming the queue 266 go func() { 267 defer close(waitCh) 268 i, _ := q.Get() 269 if i != "foo" { 270 t.Errorf("Expected %v, got %v", "foo", i) 271 } 272 // Finish that one up 273 q.Done(i) 274 }() 275 276 // give the worker some head start to avoid races 277 // on the select statement that cause flakiness 278 time.Sleep(100 * time.Millisecond) 279 // Finish the first one to unblock the other worker 280 select { 281 case <-waitCh: 282 t.Errorf("worker should be blocked until we are done") 283 default: 284 q.Done("foo") 285 } 286 287 // wait for the worker to consume the new object 288 // There should be no more objects in the queue 289 <-waitCh 290 if a := q.Len(); a != 0 { 291 t.Errorf("Expected queue to be empty. Has %v items", a) 292 } 293 294 if _, ok := tq.touched["foo"]; ok { 295 t.Errorf("Unexpected Touch") 296 } 297 } 298 299 func TestQueueDrainageUsingShutDownWithDrain(t *testing.T) { 300 301 q := workqueue.New() 302 303 q.Add("foo") 304 q.Add("bar") 305 306 firstItem, _ := q.Get() 307 secondItem, _ := q.Get() 308 309 finishedWG := sync.WaitGroup{} 310 finishedWG.Add(1) 311 go func() { 312 defer finishedWG.Done() 313 q.ShutDownWithDrain() 314 }() 315 316 // This is done as to simulate a sequence of events where ShutDownWithDrain 317 // is called before we start marking all items as done - thus simulating a 318 // drain where we wait for all items to finish processing. 319 shuttingDown := false 320 for !shuttingDown { 321 _, shuttingDown = q.Get() 322 } 323 324 // Mark the first two items as done, as to finish up 325 q.Done(firstItem) 326 q.Done(secondItem) 327 328 finishedWG.Wait() 329 } 330 331 func TestNoQueueDrainageUsingShutDown(t *testing.T) { 332 333 q := workqueue.New() 334 335 q.Add("foo") 336 q.Add("bar") 337 338 q.Get() 339 q.Get() 340 341 finishedWG := sync.WaitGroup{} 342 finishedWG.Add(1) 343 go func() { 344 defer finishedWG.Done() 345 // Invoke ShutDown: suspending the execution immediately. 346 q.ShutDown() 347 }() 348 349 // We can now do this and not have the test timeout because we didn't call 350 // Done on the first two items before arriving here. 351 finishedWG.Wait() 352 } 353 354 func TestForceQueueShutdownUsingShutDown(t *testing.T) { 355 356 q := workqueue.New() 357 358 q.Add("foo") 359 q.Add("bar") 360 361 q.Get() 362 q.Get() 363 364 finishedWG := sync.WaitGroup{} 365 finishedWG.Add(1) 366 go func() { 367 defer finishedWG.Done() 368 q.ShutDownWithDrain() 369 }() 370 371 // This is done as to simulate a sequence of events where ShutDownWithDrain 372 // is called before ShutDown 373 shuttingDown := false 374 for !shuttingDown { 375 _, shuttingDown = q.Get() 376 } 377 378 // Use ShutDown to force the queue to shut down (simulating a caller 379 // which can invoke this function on a second SIGTERM/SIGINT) 380 q.ShutDown() 381 382 // We can now do this and not have the test timeout because we didn't call 383 // done on any of the items before arriving here. 384 finishedWG.Wait() 385 } 386 387 func TestQueueDrainageUsingShutDownWithDrainWithDirtyItem(t *testing.T) { 388 q := workqueue.New() 389 390 q.Add("foo") 391 gotten, _ := q.Get() 392 q.Add("foo") 393 394 finishedWG := sync.WaitGroup{} 395 finishedWG.Add(1) 396 go func() { 397 defer finishedWG.Done() 398 q.ShutDownWithDrain() 399 }() 400 401 // Ensure that ShutDownWithDrain has started and is blocked. 402 shuttingDown := false 403 for !shuttingDown { 404 _, shuttingDown = q.Get() 405 } 406 407 // Finish "working". 408 q.Done(gotten) 409 410 // `shuttingDown` becomes false because Done caused an item to go back into 411 // the queue. 412 again, shuttingDown := q.Get() 413 if shuttingDown { 414 t.Fatalf("should not have been done") 415 } 416 q.Done(again) 417 418 // Now we are really done. 419 _, shuttingDown = q.Get() 420 if !shuttingDown { 421 t.Fatalf("should have been done") 422 } 423 424 finishedWG.Wait() 425 } 426 427 // TestGarbageCollection ensures that objects that are added then removed from the queue are 428 // able to be garbage collected. 429 func TestGarbageCollection(t *testing.T) { 430 type bigObject struct { 431 data []byte 432 } 433 leakQueue := workqueue.New() 434 t.Cleanup(func() { 435 // Make sure leakQueue doesn't go out of scope too early 436 runtime.KeepAlive(leakQueue) 437 }) 438 c := &bigObject{data: []byte("hello")} 439 mustGarbageCollect(t, c) 440 leakQueue.Add(c) 441 o, _ := leakQueue.Get() 442 leakQueue.Done(o) 443 } 444 445 // mustGarbageCollect asserts than an object was garbage collected by the end of the test. 446 // The input must be a pointer to an object. 447 func mustGarbageCollect(t *testing.T, i interface{}) { 448 t.Helper() 449 var collected int32 = 0 450 runtime.SetFinalizer(i, func(x interface{}) { 451 atomic.StoreInt32(&collected, 1) 452 }) 453 t.Cleanup(func() { 454 if err := wait.PollImmediate(time.Millisecond*100, wait.ForeverTestTimeout, func() (done bool, err error) { 455 // Trigger GC explicitly, otherwise we may need to wait a long time for it to run 456 runtime.GC() 457 return atomic.LoadInt32(&collected) == 1, nil 458 }); err != nil { 459 t.Errorf("object was not garbage collected") 460 } 461 }) 462 }