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 }