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  }