Skip to content

Commit d5fbcca

Browse files
paggynieGoogle Java Core Libraries
authored andcommitted
Test LocalCache when async refresh takes longer than expire-after-write duration.
(followup to cl/605069776 / #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
1 parent 41d0d9a commit d5fbcca

File tree

2 files changed

+173
-24
lines changed

2 files changed

+173
-24
lines changed

android/guava-tests/test/com/google/common/cache/LocalCacheTest.java

Lines changed: 79 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,12 @@
2525
import static com.google.common.cache.TestingRemovalListeners.countingRemovalListener;
2626
import static com.google.common.cache.TestingRemovalListeners.queuingRemovalListener;
2727
import static com.google.common.cache.TestingWeighers.constantWeigher;
28-
import static com.google.common.collect.Lists.newArrayList;
2928
import static com.google.common.collect.Maps.immutableEntry;
3029
import static com.google.common.truth.Truth.assertThat;
30+
import static com.google.common.util.concurrent.MoreExecutors.listeningDecorator;
31+
import static java.lang.Thread.State.WAITING;
32+
import static java.util.concurrent.Executors.newSingleThreadExecutor;
33+
import static java.util.concurrent.TimeUnit.MILLISECONDS;
3134
import static java.util.concurrent.TimeUnit.MINUTES;
3235
import static java.util.concurrent.TimeUnit.NANOSECONDS;
3336
import static java.util.concurrent.TimeUnit.SECONDS;
@@ -47,6 +50,7 @@
4750
import com.google.common.collect.ImmutableList;
4851
import com.google.common.collect.ImmutableMap;
4952
import com.google.common.collect.ImmutableSet;
53+
import com.google.common.collect.Iterables;
5054
import com.google.common.collect.Lists;
5155
import com.google.common.collect.Maps;
5256
import com.google.common.collect.testing.ConcurrentMapTestSuiteBuilder;
@@ -58,10 +62,14 @@
5862
import com.google.common.testing.NullPointerTester;
5963
import com.google.common.testing.SerializableTester;
6064
import com.google.common.testing.TestLogHandler;
65+
import com.google.common.util.concurrent.ListenableFuture;
66+
import com.google.common.util.concurrent.ListeningExecutorService;
67+
import com.google.common.util.concurrent.SettableFuture;
6168
import com.google.common.util.concurrent.UncheckedExecutionException;
6269
import java.io.Serializable;
6370
import java.lang.ref.Reference;
6471
import java.lang.ref.ReferenceQueue;
72+
import java.util.ArrayList;
6573
import java.util.Iterator;
6674
import java.util.LinkedHashMap;
6775
import java.util.List;
@@ -247,11 +255,24 @@ private void checkLogged(Throwable t) {
247255
assertSame(t, popLoggedThrowable());
248256
}
249257

258+
/*
259+
* TODO(cpovirk): Can we replace makeLocalCache with a call to builder.build()? Some tests may
260+
* need access to LocalCache APIs, but maybe we can at least make makeLocalCache use
261+
* builder.build() and then cast?
262+
*/
263+
250264
private static <K, V> LocalCache<K, V> makeLocalCache(
251265
CacheBuilder<? super K, ? super V> builder) {
252266
return new LocalCache<>(builder, null);
253267
}
254268

269+
private static <K, V> LocalCache<K, V> makeLocalCache(
270+
CacheBuilder<? super K, ? super V> builder, CacheLoader<? super K, V> loader) {
271+
return new LocalCache<>(builder, loader);
272+
}
273+
274+
// TODO(cpovirk): Inline createCacheBuilder()?
275+
255276
private static CacheBuilder<Object, Object> createCacheBuilder() {
256277
return CacheBuilder.newBuilder();
257278
}
@@ -516,6 +537,57 @@ public void testSetRefresh() {
516537
assertEquals(unit.toNanos(duration), map.refreshNanos);
517538
}
518539

540+
public void testLongAsyncRefresh() throws Exception {
541+
FakeTicker ticker = new FakeTicker();
542+
CountDownLatch reloadStarted = new CountDownLatch(1);
543+
SettableFuture<Thread> threadAboutToBlockForRefresh = SettableFuture.create();
544+
545+
ListeningExecutorService refreshExecutor = listeningDecorator(newSingleThreadExecutor());
546+
try {
547+
CacheBuilder<Object, Object> builder =
548+
createCacheBuilder()
549+
.expireAfterWrite(100, MILLISECONDS)
550+
.refreshAfterWrite(5, MILLISECONDS)
551+
.ticker(ticker);
552+
553+
CacheLoader<String, String> loader =
554+
new CacheLoader<String, String>() {
555+
@Override
556+
public String load(String key) {
557+
return key + "Load";
558+
}
559+
560+
@Override
561+
public ListenableFuture<String> reload(String key, String oldValue) {
562+
return refreshExecutor.submit(
563+
() -> {
564+
reloadStarted.countDown();
565+
566+
Thread blockingForRefresh = threadAboutToBlockForRefresh.get();
567+
while (blockingForRefresh.isAlive()
568+
&& blockingForRefresh.getState() != WAITING) {
569+
Thread.yield();
570+
}
571+
572+
return key + "Reload";
573+
});
574+
}
575+
};
576+
LocalCache<String, String> cache = makeLocalCache(builder, loader);
577+
578+
assertThat(cache.getOrLoad("test")).isEqualTo("testLoad");
579+
580+
ticker.advance(10, MILLISECONDS); // so that the next call will trigger refresh
581+
assertThat(cache.getOrLoad("test")).isEqualTo("testLoad");
582+
reloadStarted.await();
583+
ticker.advance(500, MILLISECONDS); // so that the entry expires during the reload
584+
threadAboutToBlockForRefresh.set(Thread.currentThread());
585+
assertThat(cache.getOrLoad("test")).isEqualTo("testReload");
586+
} finally {
587+
refreshExecutor.shutdown();
588+
}
589+
}
590+
519591
public void testSetRemovalListener() {
520592
RemovalListener<Object, Object> testListener = TestingRemovalListeners.nullRemovalListener();
521593
LocalCache<Object, Object> map =
@@ -584,7 +656,7 @@ public void testRecordReadOnCompute() throws ExecutionException {
584656

585657
// access some of the elements
586658
Random random = new Random();
587-
List<ReferenceEntry<Object, Object>> reads = Lists.newArrayList();
659+
List<ReferenceEntry<Object, Object>> reads = new ArrayList<>();
588660
Iterator<ReferenceEntry<Object, Object>> i = readOrder.iterator();
589661
while (i.hasNext()) {
590662
ReferenceEntry<Object, Object> entry = i.next();
@@ -2097,7 +2169,7 @@ public void testRecordRead() {
20972169

20982170
// access some of the elements
20992171
Random random = new Random();
2100-
List<ReferenceEntry<Object, Object>> reads = Lists.newArrayList();
2172+
List<ReferenceEntry<Object, Object>> reads = new ArrayList<>();
21012173
Iterator<ReferenceEntry<Object, Object>> i = readOrder.iterator();
21022174
while (i.hasNext()) {
21032175
ReferenceEntry<Object, Object> entry = i.next();
@@ -2138,7 +2210,7 @@ public void testRecordReadOnGet() {
21382210

21392211
// access some of the elements
21402212
Random random = new Random();
2141-
List<ReferenceEntry<Object, Object>> reads = Lists.newArrayList();
2213+
List<ReferenceEntry<Object, Object>> reads = new ArrayList<>();
21422214
Iterator<ReferenceEntry<Object, Object>> i = readOrder.iterator();
21432215
while (i.hasNext()) {
21442216
ReferenceEntry<Object, Object> entry = i.next();
@@ -2179,7 +2251,7 @@ public void testRecordWrite() {
21792251

21802252
// access some of the elements
21812253
Random random = new Random();
2182-
List<ReferenceEntry<Object, Object>> writes = Lists.newArrayList();
2254+
List<ReferenceEntry<Object, Object>> writes = new ArrayList<>();
21832255
Iterator<ReferenceEntry<Object, Object>> i = writeOrder.iterator();
21842256
while (i.hasNext()) {
21852257
ReferenceEntry<Object, Object> entry = i.next();
@@ -2725,7 +2797,8 @@ private void testLoadThrows(
27252797
* weakKeys and weak/softValues.
27262798
*/
27272799
private static Iterable<CacheBuilder<Object, Object>> allEntryTypeMakers() {
2728-
List<CacheBuilder<Object, Object>> result = newArrayList(allKeyValueStrengthMakers());
2800+
List<CacheBuilder<Object, Object>> result = new ArrayList<>();
2801+
Iterables.addAll(result, allKeyValueStrengthMakers());
27292802
for (CacheBuilder<Object, Object> builder : allKeyValueStrengthMakers()) {
27302803
result.add(builder.maximumSize(SMALL_MAX_SIZE));
27312804
}

0 commit comments

Comments
 (0)