diff options
author | Jing (Paggy) Nie <paggynie@amazon.com> | 2024-03-14 16:04:15 -0700 |
---|---|---|
committer | Google Java Core Libraries <jake-team+copybara@google.com> | 2024-03-14 16:06:48 -0700 |
commit | d5fbccac90aba8501c633e896ea67e2b0bfb426d (patch) | |
tree | 4a02a2b2fe7e9416511283a0ea321cca2fdb1f4d | |
parent | 41d0d9a83355dd67249791cc3867605fbfa4847e (diff) | |
download | guava-d5fbccac90aba8501c633e896ea67e2b0bfb426d.tar.gz |
Test `LocalCache` when async refresh takes longer than expire-after-write duration.
(followup to cl/605069776 / https://github.com/google/guava/pull/6851)
Also, restore the use of `ConcurrentMapTestSuiteBuilder` in the mainline. It was added in cl/94773095 but then lost during cl/132882204 in the mainline only.
Fixes #7038
RELNOTES=n/a
PiperOrigin-RevId: 615932961
-rw-r--r-- | android/guava-tests/test/com/google/common/cache/LocalCacheTest.java | 85 | ||||
-rw-r--r-- | guava-tests/test/com/google/common/cache/LocalCacheTest.java | 112 |
2 files changed, 173 insertions, 24 deletions
diff --git a/android/guava-tests/test/com/google/common/cache/LocalCacheTest.java b/android/guava-tests/test/com/google/common/cache/LocalCacheTest.java index 871d252cb..d58c048bf 100644 --- a/android/guava-tests/test/com/google/common/cache/LocalCacheTest.java +++ b/android/guava-tests/test/com/google/common/cache/LocalCacheTest.java @@ -25,9 +25,12 @@ import static com.google.common.cache.TestingCacheLoaders.identityLoader; import static com.google.common.cache.TestingRemovalListeners.countingRemovalListener; import static com.google.common.cache.TestingRemovalListeners.queuingRemovalListener; import static com.google.common.cache.TestingWeighers.constantWeigher; -import static com.google.common.collect.Lists.newArrayList; import static com.google.common.collect.Maps.immutableEntry; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.util.concurrent.MoreExecutors.listeningDecorator; +import static java.lang.Thread.State.WAITING; +import static java.util.concurrent.Executors.newSingleThreadExecutor; +import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.concurrent.TimeUnit.SECONDS; @@ -47,6 +50,7 @@ import com.google.common.cache.TestingRemovalListeners.QueuingRemovalListener; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.testing.ConcurrentMapTestSuiteBuilder; @@ -58,10 +62,14 @@ import com.google.common.testing.FakeTicker; import com.google.common.testing.NullPointerTester; import com.google.common.testing.SerializableTester; import com.google.common.testing.TestLogHandler; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.SettableFuture; import com.google.common.util.concurrent.UncheckedExecutionException; import java.io.Serializable; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; +import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; @@ -247,11 +255,24 @@ public class LocalCacheTest extends TestCase { assertSame(t, popLoggedThrowable()); } + /* + * TODO(cpovirk): Can we replace makeLocalCache with a call to builder.build()? Some tests may + * need access to LocalCache APIs, but maybe we can at least make makeLocalCache use + * builder.build() and then cast? + */ + private static <K, V> LocalCache<K, V> makeLocalCache( CacheBuilder<? super K, ? super V> builder) { return new LocalCache<>(builder, null); } + private static <K, V> LocalCache<K, V> makeLocalCache( + CacheBuilder<? super K, ? super V> builder, CacheLoader<? super K, V> loader) { + return new LocalCache<>(builder, loader); + } + + // TODO(cpovirk): Inline createCacheBuilder()? + private static CacheBuilder<Object, Object> createCacheBuilder() { return CacheBuilder.newBuilder(); } @@ -516,6 +537,57 @@ public class LocalCacheTest extends TestCase { assertEquals(unit.toNanos(duration), map.refreshNanos); } + public void testLongAsyncRefresh() throws Exception { + FakeTicker ticker = new FakeTicker(); + CountDownLatch reloadStarted = new CountDownLatch(1); + SettableFuture<Thread> threadAboutToBlockForRefresh = SettableFuture.create(); + + ListeningExecutorService refreshExecutor = listeningDecorator(newSingleThreadExecutor()); + try { + CacheBuilder<Object, Object> builder = + createCacheBuilder() + .expireAfterWrite(100, MILLISECONDS) + .refreshAfterWrite(5, MILLISECONDS) + .ticker(ticker); + + CacheLoader<String, String> loader = + new CacheLoader<String, String>() { + @Override + public String load(String key) { + return key + "Load"; + } + + @Override + public ListenableFuture<String> reload(String key, String oldValue) { + return refreshExecutor.submit( + () -> { + reloadStarted.countDown(); + + Thread blockingForRefresh = threadAboutToBlockForRefresh.get(); + while (blockingForRefresh.isAlive() + && blockingForRefresh.getState() != WAITING) { + Thread.yield(); + } + + return key + "Reload"; + }); + } + }; + LocalCache<String, String> cache = makeLocalCache(builder, loader); + + assertThat(cache.getOrLoad("test")).isEqualTo("testLoad"); + + ticker.advance(10, MILLISECONDS); // so that the next call will trigger refresh + assertThat(cache.getOrLoad("test")).isEqualTo("testLoad"); + reloadStarted.await(); + ticker.advance(500, MILLISECONDS); // so that the entry expires during the reload + threadAboutToBlockForRefresh.set(Thread.currentThread()); + assertThat(cache.getOrLoad("test")).isEqualTo("testReload"); + } finally { + refreshExecutor.shutdown(); + } + } + public void testSetRemovalListener() { RemovalListener<Object, Object> testListener = TestingRemovalListeners.nullRemovalListener(); LocalCache<Object, Object> map = @@ -584,7 +656,7 @@ public class LocalCacheTest extends TestCase { // access some of the elements Random random = new Random(); - List<ReferenceEntry<Object, Object>> reads = Lists.newArrayList(); + List<ReferenceEntry<Object, Object>> reads = new ArrayList<>(); Iterator<ReferenceEntry<Object, Object>> i = readOrder.iterator(); while (i.hasNext()) { ReferenceEntry<Object, Object> entry = i.next(); @@ -2097,7 +2169,7 @@ public class LocalCacheTest extends TestCase { // access some of the elements Random random = new Random(); - List<ReferenceEntry<Object, Object>> reads = Lists.newArrayList(); + List<ReferenceEntry<Object, Object>> reads = new ArrayList<>(); Iterator<ReferenceEntry<Object, Object>> i = readOrder.iterator(); while (i.hasNext()) { ReferenceEntry<Object, Object> entry = i.next(); @@ -2138,7 +2210,7 @@ public class LocalCacheTest extends TestCase { // access some of the elements Random random = new Random(); - List<ReferenceEntry<Object, Object>> reads = Lists.newArrayList(); + List<ReferenceEntry<Object, Object>> reads = new ArrayList<>(); Iterator<ReferenceEntry<Object, Object>> i = readOrder.iterator(); while (i.hasNext()) { ReferenceEntry<Object, Object> entry = i.next(); @@ -2179,7 +2251,7 @@ public class LocalCacheTest extends TestCase { // access some of the elements Random random = new Random(); - List<ReferenceEntry<Object, Object>> writes = Lists.newArrayList(); + List<ReferenceEntry<Object, Object>> writes = new ArrayList<>(); Iterator<ReferenceEntry<Object, Object>> i = writeOrder.iterator(); while (i.hasNext()) { ReferenceEntry<Object, Object> entry = i.next(); @@ -2725,7 +2797,8 @@ public class LocalCacheTest extends TestCase { * weakKeys and weak/softValues. */ private static Iterable<CacheBuilder<Object, Object>> allEntryTypeMakers() { - List<CacheBuilder<Object, Object>> result = newArrayList(allKeyValueStrengthMakers()); + List<CacheBuilder<Object, Object>> result = new ArrayList<>(); + Iterables.addAll(result, allKeyValueStrengthMakers()); for (CacheBuilder<Object, Object> builder : allKeyValueStrengthMakers()) { result.add(builder.maximumSize(SMALL_MAX_SIZE)); } diff --git a/guava-tests/test/com/google/common/cache/LocalCacheTest.java b/guava-tests/test/com/google/common/cache/LocalCacheTest.java index 83292d136..b0ca5d38e 100644 --- a/guava-tests/test/com/google/common/cache/LocalCacheTest.java +++ b/guava-tests/test/com/google/common/cache/LocalCacheTest.java @@ -25,9 +25,12 @@ import static com.google.common.cache.TestingCacheLoaders.identityLoader; import static com.google.common.cache.TestingRemovalListeners.countingRemovalListener; import static com.google.common.cache.TestingRemovalListeners.queuingRemovalListener; import static com.google.common.cache.TestingWeighers.constantWeigher; -import static com.google.common.collect.Lists.newArrayList; import static com.google.common.collect.Maps.immutableEntry; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.util.concurrent.MoreExecutors.listeningDecorator; +import static java.lang.Thread.State.WAITING; +import static java.util.concurrent.Executors.newSingleThreadExecutor; +import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.concurrent.TimeUnit.SECONDS; @@ -47,9 +50,10 @@ import com.google.common.cache.TestingRemovalListeners.QueuingRemovalListener; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; -import com.google.common.collect.testing.MapTestSuiteBuilder; +import com.google.common.collect.testing.ConcurrentMapTestSuiteBuilder; import com.google.common.collect.testing.TestStringMapGenerator; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; @@ -58,6 +62,9 @@ import com.google.common.testing.FakeTicker; import com.google.common.testing.NullPointerTester; import com.google.common.testing.SerializableTester; import com.google.common.testing.TestLogHandler; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.SettableFuture; import com.google.common.util.concurrent.UncheckedExecutionException; import java.io.Serializable; import java.lang.ref.Reference; @@ -104,7 +111,7 @@ public class LocalCacheTest extends TestCase { TestSuite suite = new TestSuite(); suite.addTestSuite(LocalCacheTest.class); suite.addTest( - MapTestSuiteBuilder.using(new TestStringCacheGenerator(createCacheBuilder())) + ConcurrentMapTestSuiteBuilder.using(new TestStringCacheGenerator(createCacheBuilder())) .named("LocalCache with defaults") .withFeatures( CollectionSize.ANY, @@ -112,7 +119,7 @@ public class LocalCacheTest extends TestCase { CollectionFeature.SUPPORTS_ITERATOR_REMOVE) .createTestSuite()); suite.addTest( - MapTestSuiteBuilder.using( + ConcurrentMapTestSuiteBuilder.using( new TestStringCacheGenerator(createCacheBuilder().concurrencyLevel(1))) .named("LocalCache with concurrencyLevel[1]") .withFeatures( @@ -121,7 +128,7 @@ public class LocalCacheTest extends TestCase { CollectionFeature.SUPPORTS_ITERATOR_REMOVE) .createTestSuite()); suite.addTest( - MapTestSuiteBuilder.using( + ConcurrentMapTestSuiteBuilder.using( new TestStringCacheGenerator(createCacheBuilder().maximumSize(Integer.MAX_VALUE))) .named("LocalCache with maximumSize") .withFeatures( @@ -130,7 +137,7 @@ public class LocalCacheTest extends TestCase { CollectionFeature.SUPPORTS_ITERATOR_REMOVE) .createTestSuite()); suite.addTest( - MapTestSuiteBuilder.using( + ConcurrentMapTestSuiteBuilder.using( new TestStringCacheGenerator( createCacheBuilder() .maximumWeight(Integer.MAX_VALUE) @@ -142,7 +149,8 @@ public class LocalCacheTest extends TestCase { CollectionFeature.SUPPORTS_ITERATOR_REMOVE) .createTestSuite()); suite.addTest( - MapTestSuiteBuilder.using(new TestStringCacheGenerator(createCacheBuilder().weakKeys())) + ConcurrentMapTestSuiteBuilder.using( + new TestStringCacheGenerator(createCacheBuilder().weakKeys())) .named("LocalCache with weakKeys") // keys are string literals and won't be GC'd .withFeatures( CollectionSize.ANY, @@ -150,7 +158,8 @@ public class LocalCacheTest extends TestCase { CollectionFeature.SUPPORTS_ITERATOR_REMOVE) .createTestSuite()); suite.addTest( - MapTestSuiteBuilder.using(new TestStringCacheGenerator(createCacheBuilder().weakValues())) + ConcurrentMapTestSuiteBuilder.using( + new TestStringCacheGenerator(createCacheBuilder().weakValues())) .named("LocalCache with weakValues") // values are string literals and won't be GC'd .withFeatures( CollectionSize.ANY, @@ -158,7 +167,8 @@ public class LocalCacheTest extends TestCase { CollectionFeature.SUPPORTS_ITERATOR_REMOVE) .createTestSuite()); suite.addTest( - MapTestSuiteBuilder.using(new TestStringCacheGenerator(createCacheBuilder().softValues())) + ConcurrentMapTestSuiteBuilder.using( + new TestStringCacheGenerator(createCacheBuilder().softValues())) .named("LocalCache with softValues") // values are string literals and won't be GC'd .withFeatures( CollectionSize.ANY, @@ -166,7 +176,7 @@ public class LocalCacheTest extends TestCase { CollectionFeature.SUPPORTS_ITERATOR_REMOVE) .createTestSuite()); suite.addTest( - MapTestSuiteBuilder.using( + ConcurrentMapTestSuiteBuilder.using( new TestStringCacheGenerator( createCacheBuilder() .expireAfterAccess(1, SECONDS) @@ -178,7 +188,7 @@ public class LocalCacheTest extends TestCase { CollectionFeature.SUPPORTS_ITERATOR_REMOVE) .createTestSuite()); suite.addTest( - MapTestSuiteBuilder.using( + ConcurrentMapTestSuiteBuilder.using( new TestStringCacheGenerator( createCacheBuilder() .expireAfterWrite(1, SECONDS) @@ -190,7 +200,7 @@ public class LocalCacheTest extends TestCase { CollectionFeature.SUPPORTS_ITERATOR_REMOVE) .createTestSuite()); suite.addTest( - MapTestSuiteBuilder.using( + ConcurrentMapTestSuiteBuilder.using( new TestStringCacheGenerator( createCacheBuilder() .removalListener(new SerializableRemovalListener<String, String>()))) @@ -201,7 +211,8 @@ public class LocalCacheTest extends TestCase { CollectionFeature.SUPPORTS_ITERATOR_REMOVE) .createTestSuite()); suite.addTest( - MapTestSuiteBuilder.using(new TestStringCacheGenerator(createCacheBuilder().recordStats())) + ConcurrentMapTestSuiteBuilder.using( + new TestStringCacheGenerator(createCacheBuilder().recordStats())) .named("LocalCache with recordStats") .withFeatures( CollectionSize.ANY, @@ -244,11 +255,24 @@ public class LocalCacheTest extends TestCase { assertSame(t, popLoggedThrowable()); } + /* + * TODO(cpovirk): Can we replace makeLocalCache with a call to builder.build()? Some tests may + * need access to LocalCache APIs, but maybe we can at least make makeLocalCache use + * builder.build() and then cast? + */ + private static <K, V> LocalCache<K, V> makeLocalCache( CacheBuilder<? super K, ? super V> builder) { return new LocalCache<>(builder, null); } + private static <K, V> LocalCache<K, V> makeLocalCache( + CacheBuilder<? super K, ? super V> builder, CacheLoader<? super K, V> loader) { + return new LocalCache<>(builder, loader); + } + + // TODO(cpovirk): Inline createCacheBuilder()? + private static CacheBuilder<Object, Object> createCacheBuilder() { return CacheBuilder.newBuilder(); } @@ -513,6 +537,57 @@ public class LocalCacheTest extends TestCase { assertEquals(unit.toNanos(duration), map.refreshNanos); } + public void testLongAsyncRefresh() throws Exception { + FakeTicker ticker = new FakeTicker(); + CountDownLatch reloadStarted = new CountDownLatch(1); + SettableFuture<Thread> threadAboutToBlockForRefresh = SettableFuture.create(); + + ListeningExecutorService refreshExecutor = listeningDecorator(newSingleThreadExecutor()); + try { + CacheBuilder<Object, Object> builder = + createCacheBuilder() + .expireAfterWrite(100, MILLISECONDS) + .refreshAfterWrite(5, MILLISECONDS) + .ticker(ticker); + + CacheLoader<String, String> loader = + new CacheLoader<String, String>() { + @Override + public String load(String key) { + return key + "Load"; + } + + @Override + public ListenableFuture<String> reload(String key, String oldValue) { + return refreshExecutor.submit( + () -> { + reloadStarted.countDown(); + + Thread blockingForRefresh = threadAboutToBlockForRefresh.get(); + while (blockingForRefresh.isAlive() + && blockingForRefresh.getState() != WAITING) { + Thread.yield(); + } + + return key + "Reload"; + }); + } + }; + LocalCache<String, String> cache = makeLocalCache(builder, loader); + + assertThat(cache.getOrLoad("test")).isEqualTo("testLoad"); + + ticker.advance(10, MILLISECONDS); // so that the next call will trigger refresh + assertThat(cache.getOrLoad("test")).isEqualTo("testLoad"); + reloadStarted.await(); + ticker.advance(500, MILLISECONDS); // so that the entry expires during the reload + threadAboutToBlockForRefresh.set(Thread.currentThread()); + assertThat(cache.getOrLoad("test")).isEqualTo("testReload"); + } finally { + refreshExecutor.shutdown(); + } + } + public void testSetRemovalListener() { RemovalListener<Object, Object> testListener = TestingRemovalListeners.nullRemovalListener(); LocalCache<Object, Object> map = @@ -581,7 +656,7 @@ public class LocalCacheTest extends TestCase { // access some of the elements Random random = new Random(); - List<ReferenceEntry<Object, Object>> reads = Lists.newArrayList(); + List<ReferenceEntry<Object, Object>> reads = new ArrayList<>(); Iterator<ReferenceEntry<Object, Object>> i = readOrder.iterator(); while (i.hasNext()) { ReferenceEntry<Object, Object> entry = i.next(); @@ -2146,7 +2221,7 @@ public class LocalCacheTest extends TestCase { // access some of the elements Random random = new Random(); - List<ReferenceEntry<Object, Object>> reads = Lists.newArrayList(); + List<ReferenceEntry<Object, Object>> reads = new ArrayList<>(); Iterator<ReferenceEntry<Object, Object>> i = readOrder.iterator(); while (i.hasNext()) { ReferenceEntry<Object, Object> entry = i.next(); @@ -2187,7 +2262,7 @@ public class LocalCacheTest extends TestCase { // access some of the elements Random random = new Random(); - List<ReferenceEntry<Object, Object>> reads = Lists.newArrayList(); + List<ReferenceEntry<Object, Object>> reads = new ArrayList<>(); Iterator<ReferenceEntry<Object, Object>> i = readOrder.iterator(); while (i.hasNext()) { ReferenceEntry<Object, Object> entry = i.next(); @@ -2228,7 +2303,7 @@ public class LocalCacheTest extends TestCase { // access some of the elements Random random = new Random(); - List<ReferenceEntry<Object, Object>> writes = Lists.newArrayList(); + List<ReferenceEntry<Object, Object>> writes = new ArrayList<>(); Iterator<ReferenceEntry<Object, Object>> i = writeOrder.iterator(); while (i.hasNext()) { ReferenceEntry<Object, Object> entry = i.next(); @@ -2774,7 +2849,8 @@ public class LocalCacheTest extends TestCase { * weakKeys and weak/softValues. */ private static Iterable<CacheBuilder<Object, Object>> allEntryTypeMakers() { - List<CacheBuilder<Object, Object>> result = newArrayList(allKeyValueStrengthMakers()); + List<CacheBuilder<Object, Object>> result = new ArrayList<>(); + Iterables.addAll(result, allKeyValueStrengthMakers()); for (CacheBuilder<Object, Object> builder : allKeyValueStrengthMakers()) { result.add(builder.maximumSize(SMALL_MAX_SIZE)); } |