github.com/spotmaxtech/k8s-apimachinery-v0260@v0.0.1/pkg/util/cache/expiring_test.go (about)

     1  /*
     2  Copyright 2019 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 cache
    18  
    19  import (
    20  	"context"
    21  	"math/rand"
    22  	"sync"
    23  	"testing"
    24  	"time"
    25  
    26  	"github.com/google/uuid"
    27  
    28  	testingclock "k8s.io/utils/clock/testing"
    29  )
    30  
    31  func TestExpiringCache(t *testing.T) {
    32  	cache := NewExpiring()
    33  
    34  	if result, ok := cache.Get("foo"); ok || result != nil {
    35  		t.Errorf("Expected null, false, got %#v, %v", result, ok)
    36  	}
    37  
    38  	record1 := "bob"
    39  	record2 := "alice"
    40  
    41  	// when empty, record is stored
    42  	cache.Set("foo", record1, time.Hour)
    43  	if result, ok := cache.Get("foo"); !ok || result != record1 {
    44  		t.Errorf("Expected %#v, true, got %#v, %v", record1, result, ok)
    45  	}
    46  
    47  	// newer record overrides
    48  	cache.Set("foo", record2, time.Hour)
    49  	if result, ok := cache.Get("foo"); !ok || result != record2 {
    50  		t.Errorf("Expected %#v, true, got %#v, %v", record2, result, ok)
    51  	}
    52  
    53  	// delete the current value
    54  	cache.Delete("foo")
    55  	if result, ok := cache.Get("foo"); ok || result != nil {
    56  		t.Errorf("Expected null, false, got %#v, %v", result, ok)
    57  	}
    58  }
    59  
    60  func TestExpiration(t *testing.T) {
    61  	fc := &testingclock.FakeClock{}
    62  	c := NewExpiringWithClock(fc)
    63  
    64  	c.Set("a", "a", time.Second)
    65  
    66  	fc.Step(500 * time.Millisecond)
    67  	if _, ok := c.Get("a"); !ok {
    68  		t.Fatalf("we should have found a key")
    69  	}
    70  
    71  	fc.Step(time.Second)
    72  	if _, ok := c.Get("a"); ok {
    73  		t.Fatalf("we should not have found a key")
    74  	}
    75  
    76  	c.Set("a", "a", time.Second)
    77  
    78  	fc.Step(500 * time.Millisecond)
    79  	if _, ok := c.Get("a"); !ok {
    80  		t.Fatalf("we should have found a key")
    81  	}
    82  
    83  	// reset should restart the ttl
    84  	c.Set("a", "a", time.Second)
    85  
    86  	fc.Step(750 * time.Millisecond)
    87  	if _, ok := c.Get("a"); !ok {
    88  		t.Fatalf("we should have found a key")
    89  	}
    90  
    91  	// Simulate a race between a reset and cleanup. Assert that del doesn't
    92  	// remove the key.
    93  	c.Set("a", "a", time.Second)
    94  
    95  	e := c.cache["a"]
    96  	e.generation++
    97  	e.expiry = e.expiry.Add(1 * time.Second)
    98  	c.cache["a"] = e
    99  
   100  	fc.Step(1 * time.Second)
   101  	if _, ok := c.Get("a"); !ok {
   102  		t.Fatalf("we should have found a key")
   103  	}
   104  }
   105  
   106  func TestGarbageCollection(t *testing.T) {
   107  	fc := &testingclock.FakeClock{}
   108  
   109  	type entry struct {
   110  		key, val string
   111  		ttl      time.Duration
   112  	}
   113  
   114  	tests := []struct {
   115  		name string
   116  		now  time.Time
   117  		set  []entry
   118  		want map[string]string
   119  	}{
   120  		{
   121  			name: "two entries just set",
   122  			now:  fc.Now().Add(0 * time.Second),
   123  			set: []entry{
   124  				{"a", "aa", 1 * time.Second},
   125  				{"b", "bb", 2 * time.Second},
   126  			},
   127  			want: map[string]string{
   128  				"a": "aa",
   129  				"b": "bb",
   130  			},
   131  		},
   132  		{
   133  			name: "first entry expired now",
   134  			now:  fc.Now().Add(1 * time.Second),
   135  			set: []entry{
   136  				{"a", "aa", 1 * time.Second},
   137  				{"b", "bb", 2 * time.Second},
   138  			},
   139  			want: map[string]string{
   140  				"b": "bb",
   141  			},
   142  		},
   143  		{
   144  			name: "first entry expired half a second ago",
   145  			now:  fc.Now().Add(1500 * time.Millisecond),
   146  			set: []entry{
   147  				{"a", "aa", 1 * time.Second},
   148  				{"b", "bb", 2 * time.Second},
   149  			},
   150  			want: map[string]string{
   151  				"b": "bb",
   152  			},
   153  		},
   154  		{
   155  			name: "three entries weird order",
   156  			now:  fc.Now().Add(1 * time.Second),
   157  			set: []entry{
   158  				{"c", "cc", 3 * time.Second},
   159  				{"a", "aa", 1 * time.Second},
   160  				{"b", "bb", 2 * time.Second},
   161  			},
   162  			want: map[string]string{
   163  				"b": "bb",
   164  				"c": "cc",
   165  			},
   166  		},
   167  		{
   168  			name: "expire multiple entries in one cycle",
   169  			now:  fc.Now().Add(2500 * time.Millisecond),
   170  			set: []entry{
   171  				{"a", "aa", 1 * time.Second},
   172  				{"b", "bb", 2 * time.Second},
   173  				{"c", "cc", 3 * time.Second},
   174  			},
   175  			want: map[string]string{
   176  				"c": "cc",
   177  			},
   178  		},
   179  	}
   180  
   181  	for _, test := range tests {
   182  		t.Run(test.name, func(t *testing.T) {
   183  			c := NewExpiringWithClock(fc)
   184  			for _, e := range test.set {
   185  				c.Set(e.key, e.val, e.ttl)
   186  			}
   187  
   188  			c.gc(test.now)
   189  
   190  			for k, want := range test.want {
   191  				got, ok := c.Get(k)
   192  				if !ok {
   193  					t.Errorf("expected cache to have entry for key=%q but found none", k)
   194  					continue
   195  				}
   196  				if got != want {
   197  					t.Errorf("unexpected value for key=%q: got=%q, want=%q", k, got, want)
   198  				}
   199  			}
   200  			if got, want := c.Len(), len(test.want); got != want {
   201  				t.Errorf("unexpected cache size: got=%d, want=%d", got, want)
   202  			}
   203  		})
   204  	}
   205  }
   206  
   207  func BenchmarkExpiringCacheContention(b *testing.B) {
   208  	b.Run("evict_probablility=100%", func(b *testing.B) {
   209  		benchmarkExpiringCacheContention(b, 1)
   210  	})
   211  	b.Run("evict_probablility=10%", func(b *testing.B) {
   212  		benchmarkExpiringCacheContention(b, 0.1)
   213  	})
   214  	b.Run("evict_probablility=1%", func(b *testing.B) {
   215  		benchmarkExpiringCacheContention(b, 0.01)
   216  	})
   217  }
   218  
   219  func benchmarkExpiringCacheContention(b *testing.B, prob float64) {
   220  	const numKeys = 1 << 16
   221  	cache := NewExpiring()
   222  
   223  	keys := []string{}
   224  	for i := 0; i < numKeys; i++ {
   225  		key := uuid.New().String()
   226  		keys = append(keys, key)
   227  	}
   228  
   229  	b.ResetTimer()
   230  
   231  	b.SetParallelism(256)
   232  	b.RunParallel(func(pb *testing.PB) {
   233  		rand := rand.New(rand.NewSource(rand.Int63()))
   234  		for pb.Next() {
   235  			i := rand.Int31()
   236  			key := keys[i%numKeys]
   237  			_, ok := cache.Get(key)
   238  			if ok {
   239  				// compare lower bits of sampled i to decide whether we should evict.
   240  				if rand.Float64() < prob {
   241  					cache.Delete(key)
   242  				}
   243  			} else {
   244  				cache.Set(key, struct{}{}, 50*time.Millisecond)
   245  			}
   246  		}
   247  	})
   248  }
   249  
   250  func TestStressExpiringCache(t *testing.T) {
   251  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
   252  	defer cancel()
   253  
   254  	const numKeys = 1 << 16
   255  	cache := NewExpiring()
   256  
   257  	keys := []string{}
   258  	for i := 0; i < numKeys; i++ {
   259  		key := uuid.New().String()
   260  		keys = append(keys, key)
   261  	}
   262  
   263  	var wg sync.WaitGroup
   264  	for i := 0; i < 256; i++ {
   265  		wg.Add(1)
   266  		go func() {
   267  			defer wg.Done()
   268  			rand := rand.New(rand.NewSource(rand.Int63()))
   269  			for {
   270  				select {
   271  				case <-ctx.Done():
   272  					return
   273  				default:
   274  				}
   275  				key := keys[rand.Intn(numKeys)]
   276  				if _, ok := cache.Get(key); !ok {
   277  					cache.Set(key, struct{}{}, 50*time.Millisecond)
   278  				}
   279  			}
   280  		}()
   281  	}
   282  
   283  	wg.Wait()
   284  
   285  	// trigger a GC with a set and check the cache size.
   286  	time.Sleep(60 * time.Millisecond)
   287  	cache.Set("trigger", "gc", time.Second)
   288  	if cache.Len() != 1 {
   289  		t.Errorf("unexpected cache size: got=%d, want=1", cache.Len())
   290  	}
   291  }