1 /* 2 * Copyright (C) 2009 The Guava 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 com.google.common.cache; 18 19 import static com.google.common.cache.TestingCacheLoaders.constantLoader; 20 import static com.google.common.cache.TestingCacheLoaders.identityLoader; 21 import static com.google.common.cache.TestingRemovalListeners.countingRemovalListener; 22 import static com.google.common.cache.TestingRemovalListeners.nullRemovalListener; 23 import static com.google.common.cache.TestingRemovalListeners.queuingRemovalListener; 24 import static com.google.common.cache.TestingWeighers.constantWeigher; 25 import static com.google.common.truth.Truth.assertThat; 26 import static java.util.concurrent.TimeUnit.MILLISECONDS; 27 import static java.util.concurrent.TimeUnit.NANOSECONDS; 28 import static java.util.concurrent.TimeUnit.SECONDS; 29 import static org.junit.Assert.assertThrows; 30 31 import com.google.common.annotations.GwtCompatible; 32 import com.google.common.annotations.GwtIncompatible; 33 import com.google.common.base.Ticker; 34 import com.google.common.cache.TestingRemovalListeners.CountingRemovalListener; 35 import com.google.common.cache.TestingRemovalListeners.QueuingRemovalListener; 36 import com.google.common.collect.Maps; 37 import com.google.common.collect.Sets; 38 import com.google.common.testing.NullPointerTester; 39 import java.time.Duration; 40 import java.util.Map; 41 import java.util.Random; 42 import java.util.Set; 43 import java.util.concurrent.CountDownLatch; 44 import java.util.concurrent.ExecutorService; 45 import java.util.concurrent.Executors; 46 import java.util.concurrent.Future; 47 import java.util.concurrent.atomic.AtomicBoolean; 48 import java.util.concurrent.atomic.AtomicInteger; 49 import junit.framework.TestCase; 50 51 /** Unit tests for CacheBuilder. */ 52 @GwtCompatible(emulated = true) 53 public class CacheBuilderTest extends TestCase { 54 testNewBuilder()55 public void testNewBuilder() { 56 CacheLoader<Object, Integer> loader = constantLoader(1); 57 58 LoadingCache<String, Integer> cache = 59 CacheBuilder.newBuilder().removalListener(countingRemovalListener()).build(loader); 60 61 assertEquals(Integer.valueOf(1), cache.getUnchecked("one")); 62 assertEquals(1, cache.size()); 63 } 64 testInitialCapacity_negative()65 public void testInitialCapacity_negative() { 66 CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder(); 67 try { 68 builder.initialCapacity(-1); 69 fail(); 70 } catch (IllegalArgumentException expected) { 71 } 72 } 73 testInitialCapacity_setTwice()74 public void testInitialCapacity_setTwice() { 75 CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder().initialCapacity(16); 76 try { 77 // even to the same value is not allowed 78 builder.initialCapacity(16); 79 fail(); 80 } catch (IllegalStateException expected) { 81 } 82 } 83 84 @GwtIncompatible // CacheTesting testInitialCapacity_small()85 public void testInitialCapacity_small() { 86 LoadingCache<?, ?> cache = CacheBuilder.newBuilder().initialCapacity(5).build(identityLoader()); 87 LocalCache<?, ?> map = CacheTesting.toLocalCache(cache); 88 89 assertThat(map.segments).hasLength(4); 90 assertEquals(2, map.segments[0].table.length()); 91 assertEquals(2, map.segments[1].table.length()); 92 assertEquals(2, map.segments[2].table.length()); 93 assertEquals(2, map.segments[3].table.length()); 94 } 95 96 @GwtIncompatible // CacheTesting testInitialCapacity_smallest()97 public void testInitialCapacity_smallest() { 98 LoadingCache<?, ?> cache = CacheBuilder.newBuilder().initialCapacity(0).build(identityLoader()); 99 LocalCache<?, ?> map = CacheTesting.toLocalCache(cache); 100 101 assertThat(map.segments).hasLength(4); 102 // 1 is as low as it goes, not 0. it feels dirty to know this/test this. 103 assertEquals(1, map.segments[0].table.length()); 104 assertEquals(1, map.segments[1].table.length()); 105 assertEquals(1, map.segments[2].table.length()); 106 assertEquals(1, map.segments[3].table.length()); 107 } 108 testInitialCapacity_large()109 public void testInitialCapacity_large() { 110 CacheBuilder.newBuilder().initialCapacity(Integer.MAX_VALUE); 111 // that the builder didn't blow up is enough; 112 // don't actually create this monster! 113 } 114 testConcurrencyLevel_zero()115 public void testConcurrencyLevel_zero() { 116 CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder(); 117 try { 118 builder.concurrencyLevel(0); 119 fail(); 120 } catch (IllegalArgumentException expected) { 121 } 122 } 123 testConcurrencyLevel_setTwice()124 public void testConcurrencyLevel_setTwice() { 125 CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder().concurrencyLevel(16); 126 try { 127 // even to the same value is not allowed 128 builder.concurrencyLevel(16); 129 fail(); 130 } catch (IllegalStateException expected) { 131 } 132 } 133 134 @GwtIncompatible // CacheTesting testConcurrencyLevel_small()135 public void testConcurrencyLevel_small() { 136 LoadingCache<?, ?> cache = 137 CacheBuilder.newBuilder().concurrencyLevel(1).build(identityLoader()); 138 LocalCache<?, ?> map = CacheTesting.toLocalCache(cache); 139 assertThat(map.segments).hasLength(1); 140 } 141 testConcurrencyLevel_large()142 public void testConcurrencyLevel_large() { 143 CacheBuilder.newBuilder().concurrencyLevel(Integer.MAX_VALUE); 144 // don't actually build this beast 145 } 146 testMaximumSize_negative()147 public void testMaximumSize_negative() { 148 CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder(); 149 try { 150 builder.maximumSize(-1); 151 fail(); 152 } catch (IllegalArgumentException expected) { 153 } 154 } 155 testMaximumSize_setTwice()156 public void testMaximumSize_setTwice() { 157 CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder().maximumSize(16); 158 try { 159 // even to the same value is not allowed 160 builder.maximumSize(16); 161 fail(); 162 } catch (IllegalStateException expected) { 163 } 164 } 165 166 @GwtIncompatible // maximumWeight testMaximumSize_andWeight()167 public void testMaximumSize_andWeight() { 168 CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder().maximumSize(16); 169 assertThrows(IllegalStateException.class, () -> builder.maximumWeight(16)); 170 } 171 172 @GwtIncompatible // digs into internals of the non-GWT implementation testMaximumSize_largerThanInt()173 public void testMaximumSize_largerThanInt() { 174 CacheBuilder<Object, Object> builder = 175 CacheBuilder.newBuilder().initialCapacity(512).maximumSize(Long.MAX_VALUE); 176 LocalCache<?, ?> cache = ((LocalCache.LocalManualCache<?, ?>) builder.build()).localCache; 177 assertThat(cache.segments.length * cache.segments[0].table.length()).isEqualTo(512); 178 } 179 180 @GwtIncompatible // maximumWeight testMaximumWeight_negative()181 public void testMaximumWeight_negative() { 182 CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder(); 183 assertThrows(IllegalArgumentException.class, () -> builder.maximumWeight(-1)); 184 } 185 186 @GwtIncompatible // maximumWeight testMaximumWeight_setTwice()187 public void testMaximumWeight_setTwice() { 188 CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder().maximumWeight(16); 189 assertThrows(IllegalStateException.class, () -> builder.maximumWeight(16)); 190 assertThrows(IllegalStateException.class, () -> builder.maximumSize(16)); 191 } 192 193 @GwtIncompatible // maximumWeight testMaximumWeight_withoutWeigher()194 public void testMaximumWeight_withoutWeigher() { 195 CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder().maximumWeight(1); 196 assertThrows(IllegalStateException.class, () -> builder.build(identityLoader())); 197 } 198 199 @GwtIncompatible // weigher testWeigher_withoutMaximumWeight()200 public void testWeigher_withoutMaximumWeight() { 201 CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder().weigher(constantWeigher(42)); 202 assertThrows(IllegalStateException.class, () -> builder.build(identityLoader())); 203 } 204 205 @GwtIncompatible // weigher testWeigher_withMaximumSize()206 public void testWeigher_withMaximumSize() { 207 assertThrows( 208 IllegalStateException.class, 209 () -> CacheBuilder.newBuilder().weigher(constantWeigher(42)).maximumSize(1)); 210 assertThrows( 211 IllegalStateException.class, 212 () -> CacheBuilder.newBuilder().maximumSize(1).weigher(constantWeigher(42))); 213 } 214 215 @GwtIncompatible // weakKeys testKeyStrengthSetTwice()216 public void testKeyStrengthSetTwice() { 217 CacheBuilder<Object, Object> builder1 = CacheBuilder.newBuilder().weakKeys(); 218 assertThrows(IllegalStateException.class, () -> builder1.weakKeys()); 219 } 220 221 @GwtIncompatible // weakValues testValueStrengthSetTwice()222 public void testValueStrengthSetTwice() { 223 CacheBuilder<Object, Object> builder1 = CacheBuilder.newBuilder().weakValues(); 224 assertThrows(IllegalStateException.class, () -> builder1.weakValues()); 225 assertThrows(IllegalStateException.class, () -> builder1.softValues()); 226 227 CacheBuilder<Object, Object> builder2 = CacheBuilder.newBuilder().softValues(); 228 assertThrows(IllegalStateException.class, () -> builder2.softValues()); 229 assertThrows(IllegalStateException.class, () -> builder2.weakValues()); 230 } 231 232 @GwtIncompatible // Duration 233 @SuppressWarnings("Java7ApiChecker") 234 @IgnoreJRERequirement // No more dangerous than wherever the caller got the Duration from testLargeDurationsAreOk()235 public void testLargeDurationsAreOk() { 236 Duration threeHundredYears = Duration.ofDays(365 * 300); 237 CacheBuilder<Object, Object> unused = 238 CacheBuilder.newBuilder() 239 .expireAfterWrite(threeHundredYears) 240 .expireAfterAccess(threeHundredYears) 241 .refreshAfterWrite(threeHundredYears); 242 } 243 testTimeToLive_negative()244 public void testTimeToLive_negative() { 245 CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder(); 246 try { 247 builder.expireAfterWrite(-1, SECONDS); 248 fail(); 249 } catch (IllegalArgumentException expected) { 250 } 251 } 252 253 @GwtIncompatible // Duration 254 @SuppressWarnings("Java7ApiChecker") testTimeToLive_negative_duration()255 public void testTimeToLive_negative_duration() { 256 CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder(); 257 assertThrows( 258 IllegalArgumentException.class, () -> builder.expireAfterWrite(Duration.ofSeconds(-1))); 259 } 260 261 @SuppressWarnings("ReturnValueIgnored") testTimeToLive_small()262 public void testTimeToLive_small() { 263 CacheBuilder.newBuilder().expireAfterWrite(1, NANOSECONDS).build(identityLoader()); 264 // well, it didn't blow up. 265 } 266 testTimeToLive_setTwice()267 public void testTimeToLive_setTwice() { 268 CacheBuilder<Object, Object> builder = 269 CacheBuilder.newBuilder().expireAfterWrite(3600, SECONDS); 270 try { 271 // even to the same value is not allowed 272 builder.expireAfterWrite(3600, SECONDS); 273 fail(); 274 } catch (IllegalStateException expected) { 275 } 276 } 277 278 @GwtIncompatible // Duration 279 @SuppressWarnings("Java7ApiChecker") testTimeToLive_setTwice_duration()280 public void testTimeToLive_setTwice_duration() { 281 CacheBuilder<Object, Object> builder = 282 CacheBuilder.newBuilder().expireAfterWrite(Duration.ofHours(1)); 283 assertThrows(IllegalStateException.class, () -> builder.expireAfterWrite(Duration.ofHours(1))); 284 } 285 testTimeToIdle_negative()286 public void testTimeToIdle_negative() { 287 CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder(); 288 try { 289 builder.expireAfterAccess(-1, SECONDS); 290 fail(); 291 } catch (IllegalArgumentException expected) { 292 } 293 } 294 295 @GwtIncompatible // Duration 296 @SuppressWarnings("Java7ApiChecker") testTimeToIdle_negative_duration()297 public void testTimeToIdle_negative_duration() { 298 CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder(); 299 assertThrows( 300 IllegalArgumentException.class, () -> builder.expireAfterAccess(Duration.ofSeconds(-1))); 301 } 302 303 @SuppressWarnings("ReturnValueIgnored") testTimeToIdle_small()304 public void testTimeToIdle_small() { 305 CacheBuilder.newBuilder().expireAfterAccess(1, NANOSECONDS).build(identityLoader()); 306 // well, it didn't blow up. 307 } 308 testTimeToIdle_setTwice()309 public void testTimeToIdle_setTwice() { 310 CacheBuilder<Object, Object> builder = 311 CacheBuilder.newBuilder().expireAfterAccess(3600, SECONDS); 312 try { 313 // even to the same value is not allowed 314 builder.expireAfterAccess(3600, SECONDS); 315 fail(); 316 } catch (IllegalStateException expected) { 317 } 318 } 319 320 @GwtIncompatible // Duration 321 @SuppressWarnings("Java7ApiChecker") testTimeToIdle_setTwice_duration()322 public void testTimeToIdle_setTwice_duration() { 323 CacheBuilder<Object, Object> builder = 324 CacheBuilder.newBuilder().expireAfterAccess(Duration.ofHours(1)); 325 assertThrows(IllegalStateException.class, () -> builder.expireAfterAccess(Duration.ofHours(1))); 326 } 327 328 @SuppressWarnings("Java7ApiChecker") testTimeToIdleAndToLive()329 public void testTimeToIdleAndToLive() { 330 LoadingCache<?, ?> unused = 331 CacheBuilder.newBuilder() 332 .expireAfterWrite(1, NANOSECONDS) 333 .expireAfterAccess(1, NANOSECONDS) 334 .build(identityLoader()); 335 // well, it didn't blow up. 336 } 337 338 @GwtIncompatible // refreshAfterWrite testRefresh_zero()339 public void testRefresh_zero() { 340 CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder(); 341 assertThrows(IllegalArgumentException.class, () -> builder.refreshAfterWrite(0, SECONDS)); 342 } 343 344 @GwtIncompatible // Duration 345 @SuppressWarnings("Java7ApiChecker") testRefresh_zero_duration()346 public void testRefresh_zero_duration() { 347 CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder(); 348 assertThrows(IllegalArgumentException.class, () -> builder.refreshAfterWrite(Duration.ZERO)); 349 } 350 351 @GwtIncompatible // refreshAfterWrite testRefresh_setTwice()352 public void testRefresh_setTwice() { 353 CacheBuilder<Object, Object> builder = 354 CacheBuilder.newBuilder().refreshAfterWrite(3600, SECONDS); 355 assertThrows(IllegalStateException.class, () -> builder.refreshAfterWrite(3600, SECONDS)); 356 } 357 358 @GwtIncompatible // Duration 359 @SuppressWarnings("Java7ApiChecker") testRefresh_setTwice_duration()360 public void testRefresh_setTwice_duration() { 361 CacheBuilder<Object, Object> builder = 362 CacheBuilder.newBuilder().refreshAfterWrite(Duration.ofHours(1)); 363 assertThrows(IllegalStateException.class, () -> builder.refreshAfterWrite(Duration.ofHours(1))); 364 } 365 testTicker_setTwice()366 public void testTicker_setTwice() { 367 Ticker testTicker = Ticker.systemTicker(); 368 CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder().ticker(testTicker); 369 try { 370 // even to the same instance is not allowed 371 builder.ticker(testTicker); 372 fail(); 373 } catch (IllegalStateException expected) { 374 } 375 } 376 testRemovalListener_setTwice()377 public void testRemovalListener_setTwice() { 378 RemovalListener<Object, Object> testListener = nullRemovalListener(); 379 CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder().removalListener(testListener); 380 try { 381 // even to the same instance is not allowed 382 builder = builder.removalListener(testListener); 383 fail(); 384 } catch (IllegalStateException expected) { 385 } 386 } 387 testValuesIsNotASet()388 public void testValuesIsNotASet() { 389 assertFalse(CacheBuilder.newBuilder().build().asMap().values() instanceof Set); 390 } 391 392 @GwtIncompatible // CacheTesting testNullCache()393 public void testNullCache() { 394 CountingRemovalListener<Object, Object> listener = countingRemovalListener(); 395 LoadingCache<Object, Object> nullCache = 396 CacheBuilder.newBuilder().maximumSize(0).removalListener(listener).build(identityLoader()); 397 assertEquals(0, nullCache.size()); 398 Object key = new Object(); 399 assertSame(key, nullCache.getUnchecked(key)); 400 assertEquals(1, listener.getCount()); 401 assertEquals(0, nullCache.size()); 402 CacheTesting.checkEmpty(nullCache.asMap()); 403 } 404 405 @GwtIncompatible // QueuingRemovalListener testRemovalNotification_clear()406 public void testRemovalNotification_clear() throws InterruptedException { 407 // If a clear() happens while a computation is pending, we should not get a removal 408 // notification. 409 410 final AtomicBoolean shouldWait = new AtomicBoolean(false); 411 final CountDownLatch computingLatch = new CountDownLatch(1); 412 CacheLoader<String, String> computingFunction = 413 new CacheLoader<String, String>() { 414 @Override 415 public String load(String key) throws InterruptedException { 416 if (shouldWait.get()) { 417 computingLatch.await(); 418 } 419 return key; 420 } 421 }; 422 QueuingRemovalListener<String, String> listener = queuingRemovalListener(); 423 424 final LoadingCache<String, String> cache = 425 CacheBuilder.newBuilder() 426 .concurrencyLevel(1) 427 .removalListener(listener) 428 .build(computingFunction); 429 430 // seed the map, so its segment's count > 0 431 cache.getUnchecked("a"); 432 shouldWait.set(true); 433 434 final CountDownLatch computationStarted = new CountDownLatch(1); 435 final CountDownLatch computationComplete = new CountDownLatch(1); 436 new Thread( 437 new Runnable() { 438 @Override 439 public void run() { 440 computationStarted.countDown(); 441 cache.getUnchecked("b"); 442 computationComplete.countDown(); 443 } 444 }) 445 .start(); 446 447 // wait for the computingEntry to be created 448 computationStarted.await(); 449 cache.invalidateAll(); 450 // let the computation proceed 451 computingLatch.countDown(); 452 // don't check cache.size() until we know the get("b") call is complete 453 computationComplete.await(); 454 455 // At this point, the listener should be holding the seed value (a -> a), and the map should 456 // contain the computed value (b -> b), since the clear() happened before the computation 457 // completed. 458 assertEquals(1, listener.size()); 459 RemovalNotification<String, String> notification = listener.remove(); 460 assertEquals("a", notification.getKey()); 461 assertEquals("a", notification.getValue()); 462 assertEquals(1, cache.size()); 463 assertEquals("b", cache.getUnchecked("b")); 464 } 465 466 // "Basher tests", where we throw a bunch of stuff at a LoadingCache and check basic invariants. 467 468 /** 469 * This is a less carefully-controlled version of {@link #testRemovalNotification_clear} - this is 470 * a black-box test that tries to create lots of different thread-interleavings, and asserts that 471 * each computation is affected by a call to {@code clear()} (and therefore gets passed to the 472 * removal listener), or else is not affected by the {@code clear()} (and therefore exists in the 473 * cache afterward). 474 */ 475 @GwtIncompatible // QueuingRemovalListener 476 testRemovalNotification_clear_basher()477 public void testRemovalNotification_clear_basher() throws InterruptedException { 478 // If a clear() happens close to the end of computation, one of two things should happen: 479 // - computation ends first: the removal listener is called, and the cache does not contain the 480 // key/value pair 481 // - clear() happens first: the removal listener is not called, and the cache contains the pair 482 AtomicBoolean computationShouldWait = new AtomicBoolean(); 483 CountDownLatch computationLatch = new CountDownLatch(1); 484 QueuingRemovalListener<String, String> listener = queuingRemovalListener(); 485 final LoadingCache<String, String> cache = 486 CacheBuilder.newBuilder() 487 .removalListener(listener) 488 .concurrencyLevel(20) 489 .build(new DelayingIdentityLoader<String>(computationShouldWait, computationLatch)); 490 491 int nThreads = 100; 492 int nTasks = 1000; 493 int nSeededEntries = 100; 494 Set<String> expectedKeys = Sets.newHashSetWithExpectedSize(nTasks + nSeededEntries); 495 // seed the map, so its segments have a count>0; otherwise, clear() won't visit the in-progress 496 // entries 497 for (int i = 0; i < nSeededEntries; i++) { 498 String s = "b" + i; 499 cache.getUnchecked(s); 500 expectedKeys.add(s); 501 } 502 computationShouldWait.set(true); 503 504 final AtomicInteger computedCount = new AtomicInteger(); 505 ExecutorService threadPool = Executors.newFixedThreadPool(nThreads); 506 final CountDownLatch tasksFinished = new CountDownLatch(nTasks); 507 for (int i = 0; i < nTasks; i++) { 508 final String s = "a" + i; 509 @SuppressWarnings("unused") // https://errorprone.info/bugpattern/FutureReturnValueIgnored 510 Future<?> possiblyIgnoredError = 511 threadPool.submit( 512 new Runnable() { 513 @Override 514 public void run() { 515 cache.getUnchecked(s); 516 computedCount.incrementAndGet(); 517 tasksFinished.countDown(); 518 } 519 }); 520 expectedKeys.add(s); 521 } 522 523 computationLatch.countDown(); 524 // let some computations complete 525 while (computedCount.get() < nThreads) { 526 Thread.yield(); 527 } 528 cache.invalidateAll(); 529 tasksFinished.await(); 530 531 // Check all of the removal notifications we received: they should have had correctly-associated 532 // keys and values. (An earlier bug saw removal notifications for in-progress computations, 533 // which had real keys with null values.) 534 Map<String, String> removalNotifications = Maps.newHashMap(); 535 for (RemovalNotification<String, String> notification : listener) { 536 removalNotifications.put(notification.getKey(), notification.getValue()); 537 assertEquals( 538 "Unexpected key/value pair passed to removalListener", 539 notification.getKey(), 540 notification.getValue()); 541 } 542 543 // All of the seed values should have been visible, so we should have gotten removal 544 // notifications for all of them. 545 for (int i = 0; i < nSeededEntries; i++) { 546 assertEquals("b" + i, removalNotifications.get("b" + i)); 547 } 548 549 // Each of the values added to the map should either still be there, or have seen a removal 550 // notification. 551 assertEquals(expectedKeys, Sets.union(cache.asMap().keySet(), removalNotifications.keySet())); 552 assertTrue(Sets.intersection(cache.asMap().keySet(), removalNotifications.keySet()).isEmpty()); 553 threadPool.shutdown(); 554 threadPool.awaitTermination(300, SECONDS); 555 } 556 557 /** 558 * Calls get() repeatedly from many different threads, and tests that all of the removed entries 559 * (removed because of size limits or expiration) trigger appropriate removal notifications. 560 */ 561 @GwtIncompatible // QueuingRemovalListener 562 testRemovalNotification_get_basher()563 public void testRemovalNotification_get_basher() throws InterruptedException { 564 int nTasks = 1000; 565 int nThreads = 100; 566 final int getsPerTask = 1000; 567 final int nUniqueKeys = 10000; 568 final Random random = new Random(); // Randoms.insecureRandom(); 569 570 QueuingRemovalListener<String, String> removalListener = queuingRemovalListener(); 571 final AtomicInteger computeCount = new AtomicInteger(); 572 final AtomicInteger exceptionCount = new AtomicInteger(); 573 final AtomicInteger computeNullCount = new AtomicInteger(); 574 @SuppressWarnings("CacheLoaderNull") // test of handling of erroneous implementation 575 CacheLoader<String, String> countingIdentityLoader = 576 new CacheLoader<String, String>() { 577 @Override 578 public String load(String key) throws InterruptedException { 579 int behavior = random.nextInt(4); 580 if (behavior == 0) { // throw an exception 581 exceptionCount.incrementAndGet(); 582 throw new RuntimeException("fake exception for test"); 583 } else if (behavior == 1) { // return null 584 computeNullCount.incrementAndGet(); 585 return null; 586 } else if (behavior == 2) { // slight delay before returning 587 Thread.sleep(5); 588 computeCount.incrementAndGet(); 589 return key; 590 } else { 591 computeCount.incrementAndGet(); 592 return key; 593 } 594 } 595 }; 596 final LoadingCache<String, String> cache = 597 CacheBuilder.newBuilder() 598 .recordStats() 599 .concurrencyLevel(2) 600 .expireAfterWrite(100, MILLISECONDS) 601 .removalListener(removalListener) 602 .maximumSize(5000) 603 .build(countingIdentityLoader); 604 605 ExecutorService threadPool = Executors.newFixedThreadPool(nThreads); 606 for (int i = 0; i < nTasks; i++) { 607 @SuppressWarnings("unused") // https://errorprone.info/bugpattern/FutureReturnValueIgnored 608 Future<?> possiblyIgnoredError = 609 threadPool.submit( 610 new Runnable() { 611 @Override 612 public void run() { 613 for (int j = 0; j < getsPerTask; j++) { 614 try { 615 cache.getUnchecked("key" + random.nextInt(nUniqueKeys)); 616 } catch (RuntimeException e) { 617 } 618 } 619 } 620 }); 621 } 622 623 threadPool.shutdown(); 624 threadPool.awaitTermination(300, SECONDS); 625 626 // Since we're not doing any more cache operations, and the cache only expires/evicts when doing 627 // other operations, the cache and the removal queue won't change from this point on. 628 629 // Verify that each received removal notification was valid 630 for (RemovalNotification<String, String> notification : removalListener) { 631 assertEquals("Invalid removal notification", notification.getKey(), notification.getValue()); 632 } 633 634 CacheStats stats = cache.stats(); 635 assertEquals(removalListener.size(), stats.evictionCount()); 636 assertEquals(computeCount.get(), stats.loadSuccessCount()); 637 assertEquals(exceptionCount.get() + computeNullCount.get(), stats.loadExceptionCount()); 638 // each computed value is still in the cache, or was passed to the removal listener 639 assertEquals(computeCount.get(), cache.size() + removalListener.size()); 640 } 641 642 @GwtIncompatible // NullPointerTester testNullParameters()643 public void testNullParameters() throws Exception { 644 NullPointerTester tester = new NullPointerTester(); 645 CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder(); 646 tester.testAllPublicInstanceMethods(builder); 647 } 648 649 @GwtIncompatible // CacheTesting testSizingDefaults()650 public void testSizingDefaults() { 651 LoadingCache<?, ?> cache = CacheBuilder.newBuilder().build(identityLoader()); 652 LocalCache<?, ?> map = CacheTesting.toLocalCache(cache); 653 assertThat(map.segments).hasLength(4); // concurrency level 654 assertEquals(4, map.segments[0].table.length()); // capacity / conc level 655 } 656 657 @GwtIncompatible // CountDownLatch 658 static final class DelayingIdentityLoader<T> extends CacheLoader<T, T> { 659 private final AtomicBoolean shouldWait; 660 private final CountDownLatch delayLatch; 661 DelayingIdentityLoader(AtomicBoolean shouldWait, CountDownLatch delayLatch)662 DelayingIdentityLoader(AtomicBoolean shouldWait, CountDownLatch delayLatch) { 663 this.shouldWait = shouldWait; 664 this.delayLatch = delayLatch; 665 } 666 667 @Override load(T key)668 public T load(T key) throws InterruptedException { 669 if (shouldWait.get()) { 670 delayLatch.await(); 671 } 672 return key; 673 } 674 } 675 } 676