1 /* 2 * Copyright (C) 2011 The Guava Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 * in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the License 10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 * or implied. See the License for the specific language governing permissions and limitations under 12 * the License. 13 */ 14 15 package com.google.common.cache; 16 17 import static com.google.common.cache.TestingCacheLoaders.identityLoader; 18 import static com.google.common.cache.TestingRemovalListeners.countingRemovalListener; 19 import static com.google.common.cache.TestingWeighers.constantWeigher; 20 import static com.google.common.cache.TestingWeighers.intKeyWeigher; 21 import static com.google.common.cache.TestingWeighers.intValueWeigher; 22 import static com.google.common.truth.Truth.assertThat; 23 import static java.util.Arrays.asList; 24 25 import com.google.common.cache.CacheTesting.Receiver; 26 import com.google.common.cache.TestingCacheLoaders.IdentityLoader; 27 import com.google.common.cache.TestingRemovalListeners.CountingRemovalListener; 28 import java.util.List; 29 import java.util.Set; 30 import junit.framework.TestCase; 31 32 /** 33 * Tests relating to cache eviction: what does and doesn't count toward maximumSize, what happens 34 * when maximumSize is reached, etc. 35 * 36 * @author mike nonemacher 37 */ 38 public class CacheEvictionTest extends TestCase { 39 static final int MAX_SIZE = 100; 40 testEviction_setMaxSegmentSize()41 public void testEviction_setMaxSegmentSize() { 42 IdentityLoader<Object> loader = identityLoader(); 43 for (int i = 1; i < 1000; i++) { 44 LoadingCache<Object, Object> cache = CacheBuilder.newBuilder().maximumSize(i).build(loader); 45 assertEquals(i, CacheTesting.getTotalSegmentSize(cache)); 46 } 47 } 48 testEviction_setMaxSegmentWeight()49 public void testEviction_setMaxSegmentWeight() { 50 IdentityLoader<Object> loader = identityLoader(); 51 for (int i = 1; i < 1000; i++) { 52 LoadingCache<Object, Object> cache = 53 CacheBuilder.newBuilder().maximumWeight(i).weigher(constantWeigher(1)).build(loader); 54 assertEquals(i, CacheTesting.getTotalSegmentSize(cache)); 55 } 56 } 57 testEviction_maxSizeOneSegment()58 public void testEviction_maxSizeOneSegment() { 59 IdentityLoader<Integer> loader = identityLoader(); 60 LoadingCache<Integer, Integer> cache = 61 CacheBuilder.newBuilder().concurrencyLevel(1).maximumSize(MAX_SIZE).build(loader); 62 for (int i = 0; i < 2 * MAX_SIZE; i++) { 63 cache.getUnchecked(i); 64 assertEquals(Math.min(i + 1, MAX_SIZE), cache.size()); 65 } 66 67 assertEquals(MAX_SIZE, cache.size()); 68 CacheTesting.checkValidState(cache); 69 } 70 testEviction_maxWeightOneSegment()71 public void testEviction_maxWeightOneSegment() { 72 IdentityLoader<Integer> loader = identityLoader(); 73 LoadingCache<Integer, Integer> cache = 74 CacheBuilder.newBuilder() 75 .concurrencyLevel(1) 76 .maximumWeight(2 * MAX_SIZE) 77 .weigher(constantWeigher(2)) 78 .build(loader); 79 for (int i = 0; i < 2 * MAX_SIZE; i++) { 80 cache.getUnchecked(i); 81 assertEquals(Math.min(i + 1, MAX_SIZE), cache.size()); 82 } 83 84 assertEquals(MAX_SIZE, cache.size()); 85 CacheTesting.checkValidState(cache); 86 } 87 testEviction_maxSize()88 public void testEviction_maxSize() { 89 CountingRemovalListener<Integer, Integer> removalListener = countingRemovalListener(); 90 IdentityLoader<Integer> loader = identityLoader(); 91 LoadingCache<Integer, Integer> cache = 92 CacheBuilder.newBuilder() 93 .maximumSize(MAX_SIZE) 94 .removalListener(removalListener) 95 .build(loader); 96 for (int i = 0; i < 2 * MAX_SIZE; i++) { 97 cache.getUnchecked(i); 98 assertTrue(cache.size() <= MAX_SIZE); 99 } 100 101 assertEquals(MAX_SIZE, CacheTesting.accessQueueSize(cache)); 102 assertEquals(MAX_SIZE, cache.size()); 103 CacheTesting.processPendingNotifications(cache); 104 assertEquals(MAX_SIZE, removalListener.getCount()); 105 CacheTesting.checkValidState(cache); 106 } 107 testEviction_maxWeight()108 public void testEviction_maxWeight() { 109 CountingRemovalListener<Integer, Integer> removalListener = countingRemovalListener(); 110 IdentityLoader<Integer> loader = identityLoader(); 111 LoadingCache<Integer, Integer> cache = 112 CacheBuilder.newBuilder() 113 .maximumWeight(2 * MAX_SIZE) 114 .weigher(constantWeigher(2)) 115 .removalListener(removalListener) 116 .build(loader); 117 for (int i = 0; i < 2 * MAX_SIZE; i++) { 118 cache.getUnchecked(i); 119 assertTrue(cache.size() <= MAX_SIZE); 120 } 121 122 assertEquals(MAX_SIZE, CacheTesting.accessQueueSize(cache)); 123 assertEquals(MAX_SIZE, cache.size()); 124 CacheTesting.processPendingNotifications(cache); 125 assertEquals(MAX_SIZE, removalListener.getCount()); 126 CacheTesting.checkValidState(cache); 127 } 128 129 /** 130 * With an unlimited-size cache with maxWeight of 0, entries weighing 0 should still be cached. 131 * Entries with positive weight should not be cached (nor dump existing cache). 132 */ testEviction_maxWeight_zero()133 public void testEviction_maxWeight_zero() { 134 CountingRemovalListener<Integer, Integer> removalListener = countingRemovalListener(); 135 IdentityLoader<Integer> loader = identityLoader(); 136 137 // Even numbers are free, odd are too expensive 138 Weigher<Integer, Integer> evensOnly = 139 new Weigher<Integer, Integer>() { 140 @Override 141 public int weigh(Integer k, Integer v) { 142 return k % 2; 143 } 144 }; 145 146 LoadingCache<Integer, Integer> cache = 147 CacheBuilder.newBuilder() 148 .concurrencyLevel(1) 149 .maximumWeight(0) 150 .weigher(evensOnly) 151 .removalListener(removalListener) 152 .build(loader); 153 154 // 1 won't be cached 155 assertThat(cache.getUnchecked(1)).isEqualTo(1); 156 assertThat(cache.asMap().keySet()).isEmpty(); 157 158 CacheTesting.processPendingNotifications(cache); 159 assertThat(removalListener.getCount()).isEqualTo(1); 160 161 // 2 will be cached 162 assertThat(cache.getUnchecked(2)).isEqualTo(2); 163 assertThat(cache.asMap().keySet()).containsExactly(2); 164 165 CacheTesting.processPendingNotifications(cache); 166 CacheTesting.checkValidState(cache); 167 assertThat(removalListener.getCount()).isEqualTo(1); 168 169 // 4 will be cached 170 assertThat(cache.getUnchecked(4)).isEqualTo(4); 171 assertThat(cache.asMap().keySet()).containsExactly(2, 4); 172 173 CacheTesting.processPendingNotifications(cache); 174 assertThat(removalListener.getCount()).isEqualTo(1); 175 176 // 5 won't be cached, won't dump cache 177 assertThat(cache.getUnchecked(5)).isEqualTo(5); 178 assertThat(cache.asMap().keySet()).containsExactly(2, 4); 179 180 CacheTesting.processPendingNotifications(cache); 181 assertThat(removalListener.getCount()).isEqualTo(2); 182 183 // Should we pepper more of these calls throughout the above? Where? 184 CacheTesting.checkValidState(cache); 185 } 186 187 /** 188 * Tests that when a single entry exceeds the segment's max weight, the new entry is immediately 189 * evicted and nothing else. 190 */ testEviction_maxWeight_entryTooBig()191 public void testEviction_maxWeight_entryTooBig() { 192 CountingRemovalListener<Integer, Integer> removalListener = countingRemovalListener(); 193 IdentityLoader<Integer> loader = identityLoader(); 194 195 LoadingCache<Integer, Integer> cache = 196 CacheBuilder.newBuilder() 197 .concurrencyLevel(1) 198 .maximumWeight(4) 199 .weigher(intValueWeigher()) 200 .removalListener(removalListener) 201 .build(loader); 202 203 // caches 2 204 assertThat(cache.getUnchecked(2)).isEqualTo(2); 205 assertThat(cache.asMap().keySet()).containsExactly(2); 206 207 CacheTesting.processPendingNotifications(cache); 208 assertThat(removalListener.getCount()).isEqualTo(0); 209 210 // caches 3, evicts 2 211 assertThat(cache.getUnchecked(3)).isEqualTo(3); 212 assertThat(cache.asMap().keySet()).containsExactly(3); 213 214 CacheTesting.processPendingNotifications(cache); 215 assertThat(removalListener.getCount()).isEqualTo(1); 216 217 // doesn't cache 5, doesn't evict 218 assertThat(cache.getUnchecked(5)).isEqualTo(5); 219 assertThat(cache.asMap().keySet()).containsExactly(3); 220 221 CacheTesting.processPendingNotifications(cache); 222 assertThat(removalListener.getCount()).isEqualTo(2); 223 224 // caches 1, evicts nothing 225 assertThat(cache.getUnchecked(1)).isEqualTo(1); 226 assertThat(cache.asMap().keySet()).containsExactly(3, 1); 227 228 CacheTesting.processPendingNotifications(cache); 229 assertThat(removalListener.getCount()).isEqualTo(2); 230 231 // caches 4, evicts 1 and 3 232 assertThat(cache.getUnchecked(4)).isEqualTo(4); 233 assertThat(cache.asMap().keySet()).containsExactly(4); 234 235 CacheTesting.processPendingNotifications(cache); 236 assertThat(removalListener.getCount()).isEqualTo(4); 237 238 // Should we pepper more of these calls throughout the above? Where? 239 CacheTesting.checkValidState(cache); 240 } 241 testEviction_overflow()242 public void testEviction_overflow() { 243 CountingRemovalListener<Object, Object> removalListener = countingRemovalListener(); 244 IdentityLoader<Object> loader = identityLoader(); 245 LoadingCache<Object, Object> cache = 246 CacheBuilder.newBuilder() 247 .concurrencyLevel(1) 248 .maximumWeight(1L << 31) 249 .weigher(constantWeigher(Integer.MAX_VALUE)) 250 .removalListener(removalListener) 251 .build(loader); 252 cache.getUnchecked(objectWithHash(0)); 253 cache.getUnchecked(objectWithHash(0)); 254 CacheTesting.processPendingNotifications(cache); 255 assertEquals(1, removalListener.getCount()); 256 } 257 testUpdateRecency_onGet()258 public void testUpdateRecency_onGet() { 259 IdentityLoader<Integer> loader = identityLoader(); 260 final LoadingCache<Integer, Integer> cache = 261 CacheBuilder.newBuilder().maximumSize(MAX_SIZE).build(loader); 262 CacheTesting.checkRecency( 263 cache, 264 MAX_SIZE, 265 new Receiver<ReferenceEntry<Integer, Integer>>() { 266 @Override 267 public void accept(ReferenceEntry<Integer, Integer> entry) { 268 cache.getUnchecked(entry.getKey()); 269 } 270 }); 271 } 272 testUpdateRecency_onInvalidate()273 public void testUpdateRecency_onInvalidate() { 274 IdentityLoader<Integer> loader = identityLoader(); 275 final LoadingCache<Integer, Integer> cache = 276 CacheBuilder.newBuilder().maximumSize(MAX_SIZE).concurrencyLevel(1).build(loader); 277 CacheTesting.checkRecency( 278 cache, 279 MAX_SIZE, 280 new Receiver<ReferenceEntry<Integer, Integer>>() { 281 @Override 282 public void accept(ReferenceEntry<Integer, Integer> entry) { 283 Integer key = entry.getKey(); 284 cache.invalidate(key); 285 } 286 }); 287 } 288 testEviction_lru()289 public void testEviction_lru() { 290 // test lru within a single segment 291 IdentityLoader<Integer> loader = identityLoader(); 292 LoadingCache<Integer, Integer> cache = 293 CacheBuilder.newBuilder().concurrencyLevel(1).maximumSize(10).build(loader); 294 CacheTesting.warmUp(cache, 0, 10); 295 Set<Integer> keySet = cache.asMap().keySet(); 296 assertThat(keySet).containsExactly(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); 297 298 // re-order 299 getAll(cache, asList(0, 1, 2)); 300 CacheTesting.drainRecencyQueues(cache); 301 assertThat(keySet).containsExactly(3, 4, 5, 6, 7, 8, 9, 0, 1, 2); 302 303 // evict 3, 4, 5 304 getAll(cache, asList(10, 11, 12)); 305 CacheTesting.drainRecencyQueues(cache); 306 assertThat(keySet).containsExactly(6, 7, 8, 9, 0, 1, 2, 10, 11, 12); 307 308 // re-order 309 getAll(cache, asList(6, 7, 8)); 310 CacheTesting.drainRecencyQueues(cache); 311 assertThat(keySet).containsExactly(9, 0, 1, 2, 10, 11, 12, 6, 7, 8); 312 313 // evict 9, 0, 1 314 getAll(cache, asList(13, 14, 15)); 315 CacheTesting.drainRecencyQueues(cache); 316 assertThat(keySet).containsExactly(2, 10, 11, 12, 6, 7, 8, 13, 14, 15); 317 } 318 testEviction_weightedLru()319 public void testEviction_weightedLru() { 320 // test weighted lru within a single segment 321 IdentityLoader<Integer> loader = identityLoader(); 322 LoadingCache<Integer, Integer> cache = 323 CacheBuilder.newBuilder() 324 .concurrencyLevel(1) 325 .maximumWeight(45) 326 .weigher(intKeyWeigher()) 327 .build(loader); 328 CacheTesting.warmUp(cache, 0, 10); 329 Set<Integer> keySet = cache.asMap().keySet(); 330 assertThat(keySet).containsExactly(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); 331 332 // re-order 333 getAll(cache, asList(0, 1, 2)); 334 CacheTesting.drainRecencyQueues(cache); 335 assertThat(keySet).containsExactly(3, 4, 5, 6, 7, 8, 9, 0, 1, 2); 336 337 // evict 3, 4, 5 338 getAll(cache, asList(10)); 339 CacheTesting.drainRecencyQueues(cache); 340 assertThat(keySet).containsExactly(6, 7, 8, 9, 0, 1, 2, 10); 341 342 // re-order 343 getAll(cache, asList(6, 7, 8)); 344 CacheTesting.drainRecencyQueues(cache); 345 assertThat(keySet).containsExactly(9, 0, 1, 2, 10, 6, 7, 8); 346 347 // evict 9, 1, 2, 10 348 getAll(cache, asList(15)); 349 CacheTesting.drainRecencyQueues(cache); 350 assertThat(keySet).containsExactly(0, 6, 7, 8, 15); 351 352 // fill empty space 353 getAll(cache, asList(9)); 354 CacheTesting.drainRecencyQueues(cache); 355 assertThat(keySet).containsExactly(0, 6, 7, 8, 15, 9); 356 357 // evict 6 358 getAll(cache, asList(1)); 359 CacheTesting.drainRecencyQueues(cache); 360 assertThat(keySet).containsExactly(0, 7, 8, 15, 9, 1); 361 } 362 testEviction_overweight()363 public void testEviction_overweight() { 364 // test weighted lru within a single segment 365 IdentityLoader<Integer> loader = identityLoader(); 366 LoadingCache<Integer, Integer> cache = 367 CacheBuilder.newBuilder() 368 .concurrencyLevel(1) 369 .maximumWeight(45) 370 .weigher(intKeyWeigher()) 371 .build(loader); 372 CacheTesting.warmUp(cache, 0, 10); 373 Set<Integer> keySet = cache.asMap().keySet(); 374 assertThat(keySet).containsExactly(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); 375 376 // add an at-the-maximum-weight entry 377 getAll(cache, asList(45)); 378 CacheTesting.drainRecencyQueues(cache); 379 assertThat(keySet).containsExactly(0, 45); 380 381 // add an over-the-maximum-weight entry 382 getAll(cache, asList(46)); 383 CacheTesting.drainRecencyQueues(cache); 384 assertThat(keySet).contains(0); 385 } 386 testEviction_invalidateAll()387 public void testEviction_invalidateAll() { 388 // test that .invalidateAll() resets total weight state correctly 389 IdentityLoader<Integer> loader = identityLoader(); 390 LoadingCache<Integer, Integer> cache = 391 CacheBuilder.newBuilder().concurrencyLevel(1).maximumSize(10).build(loader); 392 393 Set<Integer> keySet = cache.asMap().keySet(); 394 assertThat(keySet).isEmpty(); 395 396 // add 0, 1, 2, 3, 4 397 getAll(cache, asList(0, 1, 2, 3, 4)); 398 CacheTesting.drainRecencyQueues(cache); 399 assertThat(keySet).containsExactly(0, 1, 2, 3, 4); 400 401 // invalidate all 402 cache.invalidateAll(); 403 CacheTesting.drainRecencyQueues(cache); 404 assertThat(keySet).isEmpty(); 405 406 // add 5, 6, 7, 8, 9, 10, 11, 12 407 getAll(cache, asList(5, 6, 7, 8, 9, 10, 11, 12)); 408 CacheTesting.drainRecencyQueues(cache); 409 assertThat(keySet).containsExactly(5, 6, 7, 8, 9, 10, 11, 12); 410 } 411 getAll(LoadingCache<Integer, Integer> cache, List<Integer> keys)412 private static void getAll(LoadingCache<Integer, Integer> cache, List<Integer> keys) { 413 for (int i : keys) { 414 cache.getUnchecked(i); 415 } 416 } 417 objectWithHash(final int hash)418 private Object objectWithHash(final int hash) { 419 return new Object() { 420 @Override 421 public int hashCode() { 422 return hash; 423 } 424 }; 425 } 426 } 427