aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJing (Paggy) Nie <paggynie@amazon.com>2024-03-14 16:04:15 -0700
committerGoogle Java Core Libraries <jake-team+copybara@google.com>2024-03-14 16:06:48 -0700
commitd5fbccac90aba8501c633e896ea67e2b0bfb426d (patch)
tree4a02a2b2fe7e9416511283a0ea321cca2fdb1f4d
parent41d0d9a83355dd67249791cc3867605fbfa4847e (diff)
downloadguava-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.java85
-rw-r--r--guava-tests/test/com/google/common/cache/LocalCacheTest.java112
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));
}