2565 lines
86 KiB
Java
2565 lines
86 KiB
Java
/*
|
|
* Copyright (C) 2011 The Guava Authors
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
|
* in compliance with the License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
|
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
|
* or implied. See the License for the specific language governing permissions and limitations under
|
|
* the License.
|
|
*/
|
|
|
|
package com.google.common.cache;
|
|
|
|
import static com.google.common.cache.TestingCacheLoaders.bulkLoader;
|
|
import static com.google.common.cache.TestingCacheLoaders.constantLoader;
|
|
import static com.google.common.cache.TestingCacheLoaders.errorLoader;
|
|
import static com.google.common.cache.TestingCacheLoaders.exceptionLoader;
|
|
import static com.google.common.cache.TestingCacheLoaders.identityLoader;
|
|
import static com.google.common.cache.TestingRemovalListeners.countingRemovalListener;
|
|
import static com.google.common.truth.Truth.assertThat;
|
|
import static java.lang.Thread.currentThread;
|
|
import static java.util.Arrays.asList;
|
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
|
|
|
import com.google.common.cache.CacheLoader.InvalidCacheLoadException;
|
|
import com.google.common.cache.TestingCacheLoaders.CountingLoader;
|
|
import com.google.common.cache.TestingCacheLoaders.IdentityLoader;
|
|
import com.google.common.cache.TestingRemovalListeners.CountingRemovalListener;
|
|
import com.google.common.collect.ImmutableList;
|
|
import com.google.common.collect.ImmutableMap;
|
|
import com.google.common.collect.Lists;
|
|
import com.google.common.collect.Maps;
|
|
import com.google.common.testing.FakeTicker;
|
|
import com.google.common.testing.TestLogHandler;
|
|
import com.google.common.util.concurrent.Callables;
|
|
import com.google.common.util.concurrent.ExecutionError;
|
|
import com.google.common.util.concurrent.Futures;
|
|
import com.google.common.util.concurrent.ListenableFuture;
|
|
import com.google.common.util.concurrent.UncheckedExecutionException;
|
|
import java.io.IOException;
|
|
import java.lang.ref.WeakReference;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Map.Entry;
|
|
import java.util.concurrent.Callable;
|
|
import java.util.concurrent.ConcurrentMap;
|
|
import java.util.concurrent.CountDownLatch;
|
|
import java.util.concurrent.ExecutionException;
|
|
import java.util.concurrent.TimeUnit;
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
import java.util.concurrent.atomic.AtomicReferenceArray;
|
|
import java.util.logging.LogRecord;
|
|
import junit.framework.TestCase;
|
|
|
|
/**
|
|
* Tests relating to cache loading: concurrent loading, exceptions during loading, etc.
|
|
*
|
|
* @author mike nonemacher
|
|
*/
|
|
public class CacheLoadingTest extends TestCase {
|
|
TestLogHandler logHandler;
|
|
|
|
@Override
|
|
public void setUp() throws Exception {
|
|
super.setUp();
|
|
logHandler = new TestLogHandler();
|
|
LocalCache.logger.addHandler(logHandler);
|
|
}
|
|
|
|
@Override
|
|
public void tearDown() throws Exception {
|
|
super.tearDown();
|
|
// TODO(cpovirk): run tests in other thread instead of messing with main thread interrupt status
|
|
currentThread().interrupted();
|
|
LocalCache.logger.removeHandler(logHandler);
|
|
}
|
|
|
|
private Throwable popLoggedThrowable() {
|
|
List<LogRecord> logRecords = logHandler.getStoredLogRecords();
|
|
assertEquals(1, logRecords.size());
|
|
LogRecord logRecord = logRecords.get(0);
|
|
logHandler.clear();
|
|
return logRecord.getThrown();
|
|
}
|
|
|
|
private void checkNothingLogged() {
|
|
assertThat(logHandler.getStoredLogRecords()).isEmpty();
|
|
}
|
|
|
|
private void checkLoggedCause(Throwable t) {
|
|
assertThat(popLoggedThrowable()).hasCauseThat().isSameInstanceAs(t);
|
|
}
|
|
|
|
private void checkLoggedInvalidLoad() {
|
|
assertThat(popLoggedThrowable()).isInstanceOf(InvalidCacheLoadException.class);
|
|
}
|
|
|
|
public void testLoad() throws ExecutionException {
|
|
LoadingCache<Object, Object> cache =
|
|
CacheBuilder.newBuilder().recordStats().build(identityLoader());
|
|
CacheStats stats = cache.stats();
|
|
assertEquals(0, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
Object key = new Object();
|
|
assertSame(key, cache.get(key));
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
key = new Object();
|
|
assertSame(key, cache.getUnchecked(key));
|
|
stats = cache.stats();
|
|
assertEquals(2, stats.missCount());
|
|
assertEquals(2, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
key = new Object();
|
|
cache.refresh(key);
|
|
checkNothingLogged();
|
|
stats = cache.stats();
|
|
assertEquals(2, stats.missCount());
|
|
assertEquals(3, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
assertSame(key, cache.get(key));
|
|
stats = cache.stats();
|
|
assertEquals(2, stats.missCount());
|
|
assertEquals(3, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(1, stats.hitCount());
|
|
|
|
Object value = new Object();
|
|
// callable is not called
|
|
assertSame(key, cache.get(key, throwing(new Exception())));
|
|
stats = cache.stats();
|
|
assertEquals(2, stats.missCount());
|
|
assertEquals(3, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(2, stats.hitCount());
|
|
|
|
key = new Object();
|
|
assertSame(value, cache.get(key, Callables.returning(value)));
|
|
stats = cache.stats();
|
|
assertEquals(3, stats.missCount());
|
|
assertEquals(4, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(2, stats.hitCount());
|
|
}
|
|
|
|
public void testReload() throws ExecutionException {
|
|
final Object one = new Object();
|
|
final Object two = new Object();
|
|
CacheLoader<Object, Object> loader =
|
|
new CacheLoader<Object, Object>() {
|
|
@Override
|
|
public Object load(Object key) {
|
|
return one;
|
|
}
|
|
|
|
@Override
|
|
public ListenableFuture<Object> reload(Object key, Object oldValue) {
|
|
return Futures.immediateFuture(two);
|
|
}
|
|
};
|
|
|
|
LoadingCache<Object, Object> cache = CacheBuilder.newBuilder().recordStats().build(loader);
|
|
Object key = new Object();
|
|
CacheStats stats = cache.stats();
|
|
assertEquals(0, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
assertSame(one, cache.getUnchecked(key));
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
cache.refresh(key);
|
|
checkNothingLogged();
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(2, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
assertSame(two, cache.getUnchecked(key));
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(2, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(1, stats.hitCount());
|
|
}
|
|
|
|
public void testRefresh() {
|
|
final Object one = new Object();
|
|
final Object two = new Object();
|
|
FakeTicker ticker = new FakeTicker();
|
|
CacheLoader<Object, Object> loader =
|
|
new CacheLoader<Object, Object>() {
|
|
@Override
|
|
public Object load(Object key) {
|
|
return one;
|
|
}
|
|
|
|
@Override
|
|
public ListenableFuture<Object> reload(Object key, Object oldValue) {
|
|
return Futures.immediateFuture(two);
|
|
}
|
|
};
|
|
|
|
LoadingCache<Object, Object> cache =
|
|
CacheBuilder.newBuilder()
|
|
.recordStats()
|
|
.ticker(ticker)
|
|
.refreshAfterWrite(1, MILLISECONDS)
|
|
.build(loader);
|
|
Object key = new Object();
|
|
CacheStats stats = cache.stats();
|
|
assertEquals(0, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
assertSame(one, cache.getUnchecked(key));
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
ticker.advance(1, MILLISECONDS);
|
|
assertSame(one, cache.getUnchecked(key));
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(1, stats.hitCount());
|
|
|
|
ticker.advance(1, MILLISECONDS);
|
|
assertSame(two, cache.getUnchecked(key));
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(2, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(2, stats.hitCount());
|
|
|
|
ticker.advance(1, MILLISECONDS);
|
|
assertSame(two, cache.getUnchecked(key));
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(2, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(3, stats.hitCount());
|
|
}
|
|
|
|
public void testRefresh_getIfPresent() {
|
|
final Object one = new Object();
|
|
final Object two = new Object();
|
|
FakeTicker ticker = new FakeTicker();
|
|
CacheLoader<Object, Object> loader =
|
|
new CacheLoader<Object, Object>() {
|
|
@Override
|
|
public Object load(Object key) {
|
|
return one;
|
|
}
|
|
|
|
@Override
|
|
public ListenableFuture<Object> reload(Object key, Object oldValue) {
|
|
return Futures.immediateFuture(two);
|
|
}
|
|
};
|
|
|
|
LoadingCache<Object, Object> cache =
|
|
CacheBuilder.newBuilder()
|
|
.recordStats()
|
|
.ticker(ticker)
|
|
.refreshAfterWrite(1, MILLISECONDS)
|
|
.build(loader);
|
|
Object key = new Object();
|
|
CacheStats stats = cache.stats();
|
|
assertEquals(0, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
assertSame(one, cache.getUnchecked(key));
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
ticker.advance(1, MILLISECONDS);
|
|
assertSame(one, cache.getIfPresent(key));
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(1, stats.hitCount());
|
|
|
|
ticker.advance(1, MILLISECONDS);
|
|
assertSame(two, cache.getIfPresent(key));
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(2, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(2, stats.hitCount());
|
|
|
|
ticker.advance(1, MILLISECONDS);
|
|
assertSame(two, cache.getIfPresent(key));
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(2, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(3, stats.hitCount());
|
|
}
|
|
|
|
public void testBulkLoad_default() throws ExecutionException {
|
|
LoadingCache<Integer, Integer> cache =
|
|
CacheBuilder.newBuilder()
|
|
.recordStats()
|
|
.build(TestingCacheLoaders.<Integer>identityLoader());
|
|
CacheStats stats = cache.stats();
|
|
assertEquals(0, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
assertEquals(ImmutableMap.of(), cache.getAll(ImmutableList.<Integer>of()));
|
|
assertEquals(0, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
assertEquals(ImmutableMap.of(1, 1), cache.getAll(asList(1)));
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
assertEquals(ImmutableMap.of(1, 1, 2, 2, 3, 3, 4, 4), cache.getAll(asList(1, 2, 3, 4)));
|
|
stats = cache.stats();
|
|
assertEquals(4, stats.missCount());
|
|
assertEquals(4, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(1, stats.hitCount());
|
|
|
|
assertEquals(ImmutableMap.of(2, 2, 3, 3), cache.getAll(asList(2, 3)));
|
|
stats = cache.stats();
|
|
assertEquals(4, stats.missCount());
|
|
assertEquals(4, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(3, stats.hitCount());
|
|
|
|
// duplicate keys are ignored, and don't impact stats
|
|
assertEquals(ImmutableMap.of(4, 4, 5, 5), cache.getAll(asList(4, 5)));
|
|
stats = cache.stats();
|
|
assertEquals(5, stats.missCount());
|
|
assertEquals(5, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(4, stats.hitCount());
|
|
}
|
|
|
|
public void testBulkLoad_loadAll() throws ExecutionException {
|
|
IdentityLoader<Integer> backingLoader = identityLoader();
|
|
CacheLoader<Integer, Integer> loader = bulkLoader(backingLoader);
|
|
LoadingCache<Integer, Integer> cache = CacheBuilder.newBuilder().recordStats().build(loader);
|
|
CacheStats stats = cache.stats();
|
|
assertEquals(0, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
assertEquals(ImmutableMap.of(), cache.getAll(ImmutableList.<Integer>of()));
|
|
assertEquals(0, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
assertEquals(ImmutableMap.of(1, 1), cache.getAll(asList(1)));
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
assertEquals(ImmutableMap.of(1, 1, 2, 2, 3, 3, 4, 4), cache.getAll(asList(1, 2, 3, 4)));
|
|
stats = cache.stats();
|
|
assertEquals(4, stats.missCount());
|
|
assertEquals(2, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(1, stats.hitCount());
|
|
|
|
assertEquals(ImmutableMap.of(2, 2, 3, 3), cache.getAll(asList(2, 3)));
|
|
stats = cache.stats();
|
|
assertEquals(4, stats.missCount());
|
|
assertEquals(2, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(3, stats.hitCount());
|
|
|
|
// duplicate keys are ignored, and don't impact stats
|
|
assertEquals(ImmutableMap.of(4, 4, 5, 5), cache.getAll(asList(4, 5)));
|
|
stats = cache.stats();
|
|
assertEquals(5, stats.missCount());
|
|
assertEquals(3, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(4, stats.hitCount());
|
|
}
|
|
|
|
public void testBulkLoad_extra() throws ExecutionException {
|
|
CacheLoader<Object, Object> loader =
|
|
new CacheLoader<Object, Object>() {
|
|
@Override
|
|
public Object load(Object key) throws Exception {
|
|
return new Object();
|
|
}
|
|
|
|
@Override
|
|
public Map<Object, Object> loadAll(Iterable<?> keys) throws Exception {
|
|
Map<Object, Object> result = Maps.newHashMap();
|
|
for (Object key : keys) {
|
|
Object value = new Object();
|
|
result.put(key, value);
|
|
// add extra entries
|
|
result.put(value, key);
|
|
}
|
|
return result;
|
|
}
|
|
};
|
|
LoadingCache<Object, Object> cache = CacheBuilder.newBuilder().build(loader);
|
|
|
|
Object[] lookupKeys = new Object[] {new Object(), new Object(), new Object()};
|
|
Map<Object, Object> result = cache.getAll(asList(lookupKeys));
|
|
assertThat(result.keySet()).containsExactlyElementsIn(asList(lookupKeys));
|
|
for (Entry<Object, Object> entry : result.entrySet()) {
|
|
Object key = entry.getKey();
|
|
Object value = entry.getValue();
|
|
assertSame(value, result.get(key));
|
|
assertNull(result.get(value));
|
|
assertSame(value, cache.asMap().get(key));
|
|
assertSame(key, cache.asMap().get(value));
|
|
}
|
|
}
|
|
|
|
public void testBulkLoad_clobber() throws ExecutionException {
|
|
final Object extraKey = new Object();
|
|
final Object extraValue = new Object();
|
|
CacheLoader<Object, Object> loader =
|
|
new CacheLoader<Object, Object>() {
|
|
@Override
|
|
public Object load(Object key) throws Exception {
|
|
throw new AssertionError();
|
|
}
|
|
|
|
@Override
|
|
public Map<Object, Object> loadAll(Iterable<?> keys) throws Exception {
|
|
Map<Object, Object> result = Maps.newHashMap();
|
|
for (Object key : keys) {
|
|
Object value = new Object();
|
|
result.put(key, value);
|
|
}
|
|
result.put(extraKey, extraValue);
|
|
return result;
|
|
}
|
|
};
|
|
LoadingCache<Object, Object> cache = CacheBuilder.newBuilder().build(loader);
|
|
cache.asMap().put(extraKey, extraKey);
|
|
assertSame(extraKey, cache.asMap().get(extraKey));
|
|
|
|
Object[] lookupKeys = new Object[] {new Object(), new Object(), new Object()};
|
|
Map<Object, Object> result = cache.getAll(asList(lookupKeys));
|
|
assertThat(result.keySet()).containsExactlyElementsIn(asList(lookupKeys));
|
|
for (Entry<Object, Object> entry : result.entrySet()) {
|
|
Object key = entry.getKey();
|
|
Object value = entry.getValue();
|
|
assertSame(value, result.get(key));
|
|
assertSame(value, cache.asMap().get(key));
|
|
}
|
|
assertNull(result.get(extraKey));
|
|
assertSame(extraValue, cache.asMap().get(extraKey));
|
|
}
|
|
|
|
public void testBulkLoad_clobberNullValue() throws ExecutionException {
|
|
final Object extraKey = new Object();
|
|
final Object extraValue = new Object();
|
|
CacheLoader<Object, Object> loader =
|
|
new CacheLoader<Object, Object>() {
|
|
@Override
|
|
public Object load(Object key) throws Exception {
|
|
throw new AssertionError();
|
|
}
|
|
|
|
@Override
|
|
public Map<Object, Object> loadAll(Iterable<?> keys) throws Exception {
|
|
Map<Object, Object> result = Maps.newHashMap();
|
|
for (Object key : keys) {
|
|
Object value = new Object();
|
|
result.put(key, value);
|
|
}
|
|
result.put(extraKey, extraValue);
|
|
result.put(extraValue, null);
|
|
return result;
|
|
}
|
|
};
|
|
LoadingCache<Object, Object> cache = CacheBuilder.newBuilder().build(loader);
|
|
cache.asMap().put(extraKey, extraKey);
|
|
assertSame(extraKey, cache.asMap().get(extraKey));
|
|
|
|
Object[] lookupKeys = new Object[] {new Object(), new Object(), new Object()};
|
|
try {
|
|
cache.getAll(asList(lookupKeys));
|
|
fail();
|
|
} catch (InvalidCacheLoadException expected) {
|
|
}
|
|
|
|
for (Object key : lookupKeys) {
|
|
assertTrue(cache.asMap().containsKey(key));
|
|
}
|
|
assertSame(extraValue, cache.asMap().get(extraKey));
|
|
assertFalse(cache.asMap().containsKey(extraValue));
|
|
}
|
|
|
|
public void testBulkLoad_clobberNullKey() throws ExecutionException {
|
|
final Object extraKey = new Object();
|
|
final Object extraValue = new Object();
|
|
CacheLoader<Object, Object> loader =
|
|
new CacheLoader<Object, Object>() {
|
|
@Override
|
|
public Object load(Object key) throws Exception {
|
|
throw new AssertionError();
|
|
}
|
|
|
|
@Override
|
|
public Map<Object, Object> loadAll(Iterable<?> keys) throws Exception {
|
|
Map<Object, Object> result = Maps.newHashMap();
|
|
for (Object key : keys) {
|
|
Object value = new Object();
|
|
result.put(key, value);
|
|
}
|
|
result.put(extraKey, extraValue);
|
|
result.put(null, extraKey);
|
|
return result;
|
|
}
|
|
};
|
|
LoadingCache<Object, Object> cache = CacheBuilder.newBuilder().build(loader);
|
|
cache.asMap().put(extraKey, extraKey);
|
|
assertSame(extraKey, cache.asMap().get(extraKey));
|
|
|
|
Object[] lookupKeys = new Object[] {new Object(), new Object(), new Object()};
|
|
try {
|
|
cache.getAll(asList(lookupKeys));
|
|
fail();
|
|
} catch (InvalidCacheLoadException expected) {
|
|
}
|
|
|
|
for (Object key : lookupKeys) {
|
|
assertTrue(cache.asMap().containsKey(key));
|
|
}
|
|
assertSame(extraValue, cache.asMap().get(extraKey));
|
|
assertFalse(cache.asMap().containsValue(extraKey));
|
|
}
|
|
|
|
public void testBulkLoad_partial() throws ExecutionException {
|
|
final Object extraKey = new Object();
|
|
final Object extraValue = new Object();
|
|
CacheLoader<Object, Object> loader =
|
|
new CacheLoader<Object, Object>() {
|
|
@Override
|
|
public Object load(Object key) throws Exception {
|
|
throw new AssertionError();
|
|
}
|
|
|
|
@Override
|
|
public Map<Object, Object> loadAll(Iterable<?> keys) throws Exception {
|
|
Map<Object, Object> result = Maps.newHashMap();
|
|
// ignore request keys
|
|
result.put(extraKey, extraValue);
|
|
return result;
|
|
}
|
|
};
|
|
LoadingCache<Object, Object> cache = CacheBuilder.newBuilder().build(loader);
|
|
|
|
Object[] lookupKeys = new Object[] {new Object(), new Object(), new Object()};
|
|
try {
|
|
cache.getAll(asList(lookupKeys));
|
|
fail();
|
|
} catch (InvalidCacheLoadException expected) {
|
|
}
|
|
assertSame(extraValue, cache.asMap().get(extraKey));
|
|
}
|
|
|
|
public void testLoadNull() throws ExecutionException {
|
|
LoadingCache<Object, Object> cache =
|
|
CacheBuilder.newBuilder().recordStats().build(constantLoader(null));
|
|
CacheStats stats = cache.stats();
|
|
assertEquals(0, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
try {
|
|
cache.get(new Object());
|
|
fail();
|
|
} catch (InvalidCacheLoadException expected) {
|
|
}
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(1, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
try {
|
|
cache.getUnchecked(new Object());
|
|
fail();
|
|
} catch (InvalidCacheLoadException expected) {
|
|
}
|
|
stats = cache.stats();
|
|
assertEquals(2, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(2, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
cache.refresh(new Object());
|
|
checkLoggedInvalidLoad();
|
|
stats = cache.stats();
|
|
assertEquals(2, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(3, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
try {
|
|
cache.get(new Object(), Callables.returning(null));
|
|
fail();
|
|
} catch (InvalidCacheLoadException expected) {
|
|
}
|
|
stats = cache.stats();
|
|
assertEquals(3, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(4, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
try {
|
|
cache.getAll(asList(new Object()));
|
|
fail();
|
|
} catch (InvalidCacheLoadException expected) {
|
|
}
|
|
stats = cache.stats();
|
|
assertEquals(4, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(5, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
}
|
|
|
|
public void testReloadNull() throws ExecutionException {
|
|
final Object one = new Object();
|
|
CacheLoader<Object, Object> loader =
|
|
new CacheLoader<Object, Object>() {
|
|
@Override
|
|
public Object load(Object key) {
|
|
return one;
|
|
}
|
|
|
|
@Override
|
|
public ListenableFuture<Object> reload(Object key, Object oldValue) {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
LoadingCache<Object, Object> cache = CacheBuilder.newBuilder().recordStats().build(loader);
|
|
Object key = new Object();
|
|
CacheStats stats = cache.stats();
|
|
assertEquals(0, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
assertSame(one, cache.getUnchecked(key));
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
cache.refresh(key);
|
|
checkLoggedInvalidLoad();
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(1, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
assertSame(one, cache.getUnchecked(key));
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(1, stats.loadExceptionCount());
|
|
assertEquals(1, stats.hitCount());
|
|
}
|
|
|
|
public void testReloadNullFuture() throws ExecutionException {
|
|
final Object one = new Object();
|
|
CacheLoader<Object, Object> loader =
|
|
new CacheLoader<Object, Object>() {
|
|
@Override
|
|
public Object load(Object key) {
|
|
return one;
|
|
}
|
|
|
|
@Override
|
|
public ListenableFuture<Object> reload(Object key, Object oldValue) {
|
|
return Futures.immediateFuture(null);
|
|
}
|
|
};
|
|
|
|
LoadingCache<Object, Object> cache = CacheBuilder.newBuilder().recordStats().build(loader);
|
|
Object key = new Object();
|
|
CacheStats stats = cache.stats();
|
|
assertEquals(0, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
assertSame(one, cache.getUnchecked(key));
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
cache.refresh(key);
|
|
checkLoggedInvalidLoad();
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(1, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
assertSame(one, cache.getUnchecked(key));
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(1, stats.loadExceptionCount());
|
|
assertEquals(1, stats.hitCount());
|
|
}
|
|
|
|
public void testRefreshNull() {
|
|
final Object one = new Object();
|
|
FakeTicker ticker = new FakeTicker();
|
|
CacheLoader<Object, Object> loader =
|
|
new CacheLoader<Object, Object>() {
|
|
@Override
|
|
public Object load(Object key) {
|
|
return one;
|
|
}
|
|
|
|
@Override
|
|
public ListenableFuture<Object> reload(Object key, Object oldValue) {
|
|
return Futures.immediateFuture(null);
|
|
}
|
|
};
|
|
|
|
LoadingCache<Object, Object> cache =
|
|
CacheBuilder.newBuilder()
|
|
.recordStats()
|
|
.ticker(ticker)
|
|
.refreshAfterWrite(1, MILLISECONDS)
|
|
.build(loader);
|
|
Object key = new Object();
|
|
CacheStats stats = cache.stats();
|
|
assertEquals(0, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
assertSame(one, cache.getUnchecked(key));
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
ticker.advance(1, MILLISECONDS);
|
|
assertSame(one, cache.getUnchecked(key));
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(1, stats.hitCount());
|
|
|
|
ticker.advance(1, MILLISECONDS);
|
|
assertSame(one, cache.getUnchecked(key));
|
|
// refreshed
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(1, stats.loadExceptionCount());
|
|
assertEquals(2, stats.hitCount());
|
|
|
|
ticker.advance(1, MILLISECONDS);
|
|
assertSame(one, cache.getUnchecked(key));
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(2, stats.loadExceptionCount());
|
|
assertEquals(3, stats.hitCount());
|
|
}
|
|
|
|
public void testBulkLoadNull() throws ExecutionException {
|
|
LoadingCache<Object, Object> cache =
|
|
CacheBuilder.newBuilder().recordStats().build(bulkLoader(constantLoader(null)));
|
|
CacheStats stats = cache.stats();
|
|
assertEquals(0, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
try {
|
|
cache.getAll(asList(new Object()));
|
|
fail();
|
|
} catch (InvalidCacheLoadException expected) {
|
|
}
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(1, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
}
|
|
|
|
public void testBulkLoadNullMap() throws ExecutionException {
|
|
LoadingCache<Object, Object> cache =
|
|
CacheBuilder.newBuilder()
|
|
.recordStats()
|
|
.build(
|
|
new CacheLoader<Object, Object>() {
|
|
@Override
|
|
public Object load(Object key) {
|
|
throw new AssertionError();
|
|
}
|
|
|
|
@Override
|
|
public Map<Object, Object> loadAll(Iterable<?> keys) {
|
|
return null;
|
|
}
|
|
});
|
|
|
|
CacheStats stats = cache.stats();
|
|
assertEquals(0, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
try {
|
|
cache.getAll(asList(new Object()));
|
|
fail();
|
|
} catch (InvalidCacheLoadException expected) {
|
|
}
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(1, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
}
|
|
|
|
public void testLoadError() throws ExecutionException {
|
|
Error e = new Error();
|
|
CacheLoader<Object, Object> loader = errorLoader(e);
|
|
LoadingCache<Object, Object> cache = CacheBuilder.newBuilder().recordStats().build(loader);
|
|
CacheStats stats = cache.stats();
|
|
assertEquals(0, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
try {
|
|
cache.get(new Object());
|
|
fail();
|
|
} catch (ExecutionError expected) {
|
|
assertThat(expected).hasCauseThat().isSameInstanceAs(e);
|
|
}
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(1, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
try {
|
|
cache.getUnchecked(new Object());
|
|
fail();
|
|
} catch (ExecutionError expected) {
|
|
assertThat(expected).hasCauseThat().isSameInstanceAs(e);
|
|
}
|
|
stats = cache.stats();
|
|
assertEquals(2, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(2, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
cache.refresh(new Object());
|
|
checkLoggedCause(e);
|
|
stats = cache.stats();
|
|
assertEquals(2, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(3, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
final Error callableError = new Error();
|
|
try {
|
|
cache.get(
|
|
new Object(),
|
|
new Callable<Object>() {
|
|
@Override
|
|
public Object call() {
|
|
throw callableError;
|
|
}
|
|
});
|
|
fail();
|
|
} catch (ExecutionError expected) {
|
|
assertThat(expected).hasCauseThat().isSameInstanceAs(callableError);
|
|
}
|
|
stats = cache.stats();
|
|
assertEquals(3, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(4, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
try {
|
|
cache.getAll(asList(new Object()));
|
|
fail();
|
|
} catch (ExecutionError expected) {
|
|
assertThat(expected).hasCauseThat().isSameInstanceAs(e);
|
|
}
|
|
stats = cache.stats();
|
|
assertEquals(4, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(5, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
}
|
|
|
|
public void testReloadError() throws ExecutionException {
|
|
final Object one = new Object();
|
|
final Error e = new Error();
|
|
CacheLoader<Object, Object> loader =
|
|
new CacheLoader<Object, Object>() {
|
|
@Override
|
|
public Object load(Object key) {
|
|
return one;
|
|
}
|
|
|
|
@Override
|
|
public ListenableFuture<Object> reload(Object key, Object oldValue) {
|
|
throw e;
|
|
}
|
|
};
|
|
|
|
LoadingCache<Object, Object> cache = CacheBuilder.newBuilder().recordStats().build(loader);
|
|
Object key = new Object();
|
|
CacheStats stats = cache.stats();
|
|
assertEquals(0, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
assertSame(one, cache.getUnchecked(key));
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
cache.refresh(key);
|
|
checkLoggedCause(e);
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(1, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
assertSame(one, cache.getUnchecked(key));
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(1, stats.loadExceptionCount());
|
|
assertEquals(1, stats.hitCount());
|
|
}
|
|
|
|
public void testReloadFutureError() throws ExecutionException {
|
|
final Object one = new Object();
|
|
final Error e = new Error();
|
|
CacheLoader<Object, Object> loader =
|
|
new CacheLoader<Object, Object>() {
|
|
@Override
|
|
public Object load(Object key) {
|
|
return one;
|
|
}
|
|
|
|
@Override
|
|
public ListenableFuture<Object> reload(Object key, Object oldValue) {
|
|
return Futures.immediateFailedFuture(e);
|
|
}
|
|
};
|
|
|
|
LoadingCache<Object, Object> cache = CacheBuilder.newBuilder().recordStats().build(loader);
|
|
Object key = new Object();
|
|
CacheStats stats = cache.stats();
|
|
assertEquals(0, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
assertSame(one, cache.getUnchecked(key));
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
cache.refresh(key);
|
|
checkLoggedCause(e);
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(1, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
assertSame(one, cache.getUnchecked(key));
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(1, stats.loadExceptionCount());
|
|
assertEquals(1, stats.hitCount());
|
|
}
|
|
|
|
public void testRefreshError() {
|
|
final Object one = new Object();
|
|
final Error e = new Error();
|
|
FakeTicker ticker = new FakeTicker();
|
|
CacheLoader<Object, Object> loader =
|
|
new CacheLoader<Object, Object>() {
|
|
@Override
|
|
public Object load(Object key) {
|
|
return one;
|
|
}
|
|
|
|
@Override
|
|
public ListenableFuture<Object> reload(Object key, Object oldValue) {
|
|
return Futures.immediateFailedFuture(e);
|
|
}
|
|
};
|
|
|
|
LoadingCache<Object, Object> cache =
|
|
CacheBuilder.newBuilder()
|
|
.recordStats()
|
|
.ticker(ticker)
|
|
.refreshAfterWrite(1, MILLISECONDS)
|
|
.build(loader);
|
|
Object key = new Object();
|
|
CacheStats stats = cache.stats();
|
|
assertEquals(0, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
assertSame(one, cache.getUnchecked(key));
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
ticker.advance(1, MILLISECONDS);
|
|
assertSame(one, cache.getUnchecked(key));
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(1, stats.hitCount());
|
|
|
|
ticker.advance(1, MILLISECONDS);
|
|
assertSame(one, cache.getUnchecked(key));
|
|
// refreshed
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(1, stats.loadExceptionCount());
|
|
assertEquals(2, stats.hitCount());
|
|
|
|
ticker.advance(1, MILLISECONDS);
|
|
assertSame(one, cache.getUnchecked(key));
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(2, stats.loadExceptionCount());
|
|
assertEquals(3, stats.hitCount());
|
|
}
|
|
|
|
public void testBulkLoadError() throws ExecutionException {
|
|
Error e = new Error();
|
|
CacheLoader<Object, Object> loader = errorLoader(e);
|
|
LoadingCache<Object, Object> cache =
|
|
CacheBuilder.newBuilder().recordStats().build(bulkLoader(loader));
|
|
CacheStats stats = cache.stats();
|
|
assertEquals(0, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
try {
|
|
cache.getAll(asList(new Object()));
|
|
fail();
|
|
} catch (ExecutionError expected) {
|
|
assertThat(expected).hasCauseThat().isSameInstanceAs(e);
|
|
}
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(1, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
}
|
|
|
|
public void testLoadCheckedException() {
|
|
Exception e = new Exception();
|
|
CacheLoader<Object, Object> loader = exceptionLoader(e);
|
|
LoadingCache<Object, Object> cache = CacheBuilder.newBuilder().recordStats().build(loader);
|
|
CacheStats stats = cache.stats();
|
|
assertEquals(0, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
try {
|
|
cache.get(new Object());
|
|
fail();
|
|
} catch (ExecutionException expected) {
|
|
assertThat(expected).hasCauseThat().isSameInstanceAs(e);
|
|
}
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(1, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
try {
|
|
cache.getUnchecked(new Object());
|
|
fail();
|
|
} catch (UncheckedExecutionException expected) {
|
|
assertThat(expected).hasCauseThat().isSameInstanceAs(e);
|
|
}
|
|
stats = cache.stats();
|
|
assertEquals(2, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(2, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
cache.refresh(new Object());
|
|
checkLoggedCause(e);
|
|
stats = cache.stats();
|
|
assertEquals(2, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(3, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
Exception callableException = new Exception();
|
|
try {
|
|
cache.get(new Object(), throwing(callableException));
|
|
fail();
|
|
} catch (ExecutionException expected) {
|
|
assertThat(expected).hasCauseThat().isSameInstanceAs(callableException);
|
|
}
|
|
stats = cache.stats();
|
|
assertEquals(3, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(4, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
try {
|
|
cache.getAll(asList(new Object()));
|
|
fail();
|
|
} catch (ExecutionException expected) {
|
|
assertThat(expected).hasCauseThat().isSameInstanceAs(e);
|
|
}
|
|
stats = cache.stats();
|
|
assertEquals(4, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(5, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
}
|
|
|
|
public void testLoadInterruptedException() {
|
|
Exception e = new InterruptedException();
|
|
CacheLoader<Object, Object> loader = exceptionLoader(e);
|
|
LoadingCache<Object, Object> cache = CacheBuilder.newBuilder().recordStats().build(loader);
|
|
CacheStats stats = cache.stats();
|
|
assertEquals(0, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
// Sanity check:
|
|
assertFalse(currentThread().interrupted());
|
|
|
|
try {
|
|
cache.get(new Object());
|
|
fail();
|
|
} catch (ExecutionException expected) {
|
|
assertThat(expected).hasCauseThat().isSameInstanceAs(e);
|
|
}
|
|
assertTrue(currentThread().interrupted());
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(1, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
try {
|
|
cache.getUnchecked(new Object());
|
|
fail();
|
|
} catch (UncheckedExecutionException expected) {
|
|
assertThat(expected).hasCauseThat().isSameInstanceAs(e);
|
|
}
|
|
assertTrue(currentThread().interrupted());
|
|
stats = cache.stats();
|
|
assertEquals(2, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(2, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
cache.refresh(new Object());
|
|
assertTrue(currentThread().interrupted());
|
|
checkLoggedCause(e);
|
|
stats = cache.stats();
|
|
assertEquals(2, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(3, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
Exception callableException = new InterruptedException();
|
|
try {
|
|
cache.get(new Object(), throwing(callableException));
|
|
fail();
|
|
} catch (ExecutionException expected) {
|
|
assertThat(expected).hasCauseThat().isSameInstanceAs(callableException);
|
|
}
|
|
assertTrue(currentThread().interrupted());
|
|
stats = cache.stats();
|
|
assertEquals(3, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(4, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
try {
|
|
cache.getAll(asList(new Object()));
|
|
fail();
|
|
} catch (ExecutionException expected) {
|
|
assertThat(expected).hasCauseThat().isSameInstanceAs(e);
|
|
}
|
|
assertTrue(currentThread().interrupted());
|
|
stats = cache.stats();
|
|
assertEquals(4, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(5, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
}
|
|
|
|
public void testReloadCheckedException() {
|
|
final Object one = new Object();
|
|
final Exception e = new Exception();
|
|
CacheLoader<Object, Object> loader =
|
|
new CacheLoader<Object, Object>() {
|
|
@Override
|
|
public Object load(Object key) {
|
|
return one;
|
|
}
|
|
|
|
@Override
|
|
public ListenableFuture<Object> reload(Object key, Object oldValue) throws Exception {
|
|
throw e;
|
|
}
|
|
};
|
|
|
|
LoadingCache<Object, Object> cache = CacheBuilder.newBuilder().recordStats().build(loader);
|
|
Object key = new Object();
|
|
CacheStats stats = cache.stats();
|
|
assertEquals(0, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
assertSame(one, cache.getUnchecked(key));
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
cache.refresh(key);
|
|
checkLoggedCause(e);
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(1, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
assertSame(one, cache.getUnchecked(key));
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(1, stats.loadExceptionCount());
|
|
assertEquals(1, stats.hitCount());
|
|
}
|
|
|
|
public void testReloadFutureCheckedException() {
|
|
final Object one = new Object();
|
|
final Exception e = new Exception();
|
|
CacheLoader<Object, Object> loader =
|
|
new CacheLoader<Object, Object>() {
|
|
@Override
|
|
public Object load(Object key) {
|
|
return one;
|
|
}
|
|
|
|
@Override
|
|
public ListenableFuture<Object> reload(Object key, Object oldValue) {
|
|
return Futures.immediateFailedFuture(e);
|
|
}
|
|
};
|
|
|
|
LoadingCache<Object, Object> cache = CacheBuilder.newBuilder().recordStats().build(loader);
|
|
Object key = new Object();
|
|
CacheStats stats = cache.stats();
|
|
assertEquals(0, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
assertSame(one, cache.getUnchecked(key));
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
cache.refresh(key);
|
|
checkLoggedCause(e);
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(1, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
assertSame(one, cache.getUnchecked(key));
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(1, stats.loadExceptionCount());
|
|
assertEquals(1, stats.hitCount());
|
|
}
|
|
|
|
public void testRefreshCheckedException() {
|
|
final Object one = new Object();
|
|
final Exception e = new Exception();
|
|
FakeTicker ticker = new FakeTicker();
|
|
CacheLoader<Object, Object> loader =
|
|
new CacheLoader<Object, Object>() {
|
|
@Override
|
|
public Object load(Object key) {
|
|
return one;
|
|
}
|
|
|
|
@Override
|
|
public ListenableFuture<Object> reload(Object key, Object oldValue) {
|
|
return Futures.immediateFailedFuture(e);
|
|
}
|
|
};
|
|
|
|
LoadingCache<Object, Object> cache =
|
|
CacheBuilder.newBuilder()
|
|
.recordStats()
|
|
.ticker(ticker)
|
|
.refreshAfterWrite(1, MILLISECONDS)
|
|
.build(loader);
|
|
Object key = new Object();
|
|
CacheStats stats = cache.stats();
|
|
assertEquals(0, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
assertSame(one, cache.getUnchecked(key));
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
ticker.advance(1, MILLISECONDS);
|
|
assertSame(one, cache.getUnchecked(key));
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(1, stats.hitCount());
|
|
|
|
ticker.advance(1, MILLISECONDS);
|
|
assertSame(one, cache.getUnchecked(key));
|
|
// refreshed
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(1, stats.loadExceptionCount());
|
|
assertEquals(2, stats.hitCount());
|
|
|
|
ticker.advance(1, MILLISECONDS);
|
|
assertSame(one, cache.getUnchecked(key));
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(2, stats.loadExceptionCount());
|
|
assertEquals(3, stats.hitCount());
|
|
}
|
|
|
|
public void testBulkLoadCheckedException() {
|
|
Exception e = new Exception();
|
|
CacheLoader<Object, Object> loader = exceptionLoader(e);
|
|
LoadingCache<Object, Object> cache =
|
|
CacheBuilder.newBuilder().recordStats().build(bulkLoader(loader));
|
|
CacheStats stats = cache.stats();
|
|
assertEquals(0, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
try {
|
|
cache.getAll(asList(new Object()));
|
|
fail();
|
|
} catch (ExecutionException expected) {
|
|
assertThat(expected).hasCauseThat().isSameInstanceAs(e);
|
|
}
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(1, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
}
|
|
|
|
public void testBulkLoadInterruptedException() {
|
|
Exception e = new InterruptedException();
|
|
CacheLoader<Object, Object> loader = exceptionLoader(e);
|
|
LoadingCache<Object, Object> cache =
|
|
CacheBuilder.newBuilder().recordStats().build(bulkLoader(loader));
|
|
CacheStats stats = cache.stats();
|
|
assertEquals(0, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
try {
|
|
cache.getAll(asList(new Object()));
|
|
fail();
|
|
} catch (ExecutionException expected) {
|
|
assertThat(expected).hasCauseThat().isSameInstanceAs(e);
|
|
}
|
|
assertTrue(currentThread().interrupted());
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(1, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
}
|
|
|
|
public void testLoadUncheckedException() throws ExecutionException {
|
|
Exception e = new RuntimeException();
|
|
CacheLoader<Object, Object> loader = exceptionLoader(e);
|
|
LoadingCache<Object, Object> cache = CacheBuilder.newBuilder().recordStats().build(loader);
|
|
CacheStats stats = cache.stats();
|
|
assertEquals(0, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
try {
|
|
cache.get(new Object());
|
|
fail();
|
|
} catch (UncheckedExecutionException expected) {
|
|
assertThat(expected).hasCauseThat().isSameInstanceAs(e);
|
|
}
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(1, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
try {
|
|
cache.getUnchecked(new Object());
|
|
fail();
|
|
} catch (UncheckedExecutionException expected) {
|
|
assertThat(expected).hasCauseThat().isSameInstanceAs(e);
|
|
}
|
|
stats = cache.stats();
|
|
assertEquals(2, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(2, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
cache.refresh(new Object());
|
|
checkLoggedCause(e);
|
|
stats = cache.stats();
|
|
assertEquals(2, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(3, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
Exception callableException = new RuntimeException();
|
|
try {
|
|
cache.get(new Object(), throwing(callableException));
|
|
fail();
|
|
} catch (UncheckedExecutionException expected) {
|
|
assertThat(expected).hasCauseThat().isSameInstanceAs(callableException);
|
|
}
|
|
stats = cache.stats();
|
|
assertEquals(3, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(4, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
try {
|
|
cache.getAll(asList(new Object()));
|
|
fail();
|
|
} catch (UncheckedExecutionException expected) {
|
|
assertThat(expected).hasCauseThat().isSameInstanceAs(e);
|
|
}
|
|
stats = cache.stats();
|
|
assertEquals(4, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(5, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
}
|
|
|
|
public void testReloadUncheckedException() throws ExecutionException {
|
|
final Object one = new Object();
|
|
final Exception e = new RuntimeException();
|
|
CacheLoader<Object, Object> loader =
|
|
new CacheLoader<Object, Object>() {
|
|
@Override
|
|
public Object load(Object key) {
|
|
return one;
|
|
}
|
|
|
|
@Override
|
|
public ListenableFuture<Object> reload(Object key, Object oldValue) throws Exception {
|
|
throw e;
|
|
}
|
|
};
|
|
|
|
LoadingCache<Object, Object> cache = CacheBuilder.newBuilder().recordStats().build(loader);
|
|
Object key = new Object();
|
|
CacheStats stats = cache.stats();
|
|
assertEquals(0, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
assertSame(one, cache.getUnchecked(key));
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
cache.refresh(key);
|
|
checkLoggedCause(e);
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(1, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
assertSame(one, cache.getUnchecked(key));
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(1, stats.loadExceptionCount());
|
|
assertEquals(1, stats.hitCount());
|
|
}
|
|
|
|
public void testReloadFutureUncheckedException() throws ExecutionException {
|
|
final Object one = new Object();
|
|
final Exception e = new RuntimeException();
|
|
CacheLoader<Object, Object> loader =
|
|
new CacheLoader<Object, Object>() {
|
|
@Override
|
|
public Object load(Object key) {
|
|
return one;
|
|
}
|
|
|
|
@Override
|
|
public ListenableFuture<Object> reload(Object key, Object oldValue) {
|
|
return Futures.immediateFailedFuture(e);
|
|
}
|
|
};
|
|
|
|
LoadingCache<Object, Object> cache = CacheBuilder.newBuilder().recordStats().build(loader);
|
|
Object key = new Object();
|
|
CacheStats stats = cache.stats();
|
|
assertEquals(0, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
assertSame(one, cache.getUnchecked(key));
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
cache.refresh(key);
|
|
checkLoggedCause(e);
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(1, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
assertSame(one, cache.getUnchecked(key));
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(1, stats.loadExceptionCount());
|
|
assertEquals(1, stats.hitCount());
|
|
}
|
|
|
|
public void testRefreshUncheckedException() {
|
|
final Object one = new Object();
|
|
final Exception e = new RuntimeException();
|
|
FakeTicker ticker = new FakeTicker();
|
|
CacheLoader<Object, Object> loader =
|
|
new CacheLoader<Object, Object>() {
|
|
@Override
|
|
public Object load(Object key) {
|
|
return one;
|
|
}
|
|
|
|
@Override
|
|
public ListenableFuture<Object> reload(Object key, Object oldValue) {
|
|
return Futures.immediateFailedFuture(e);
|
|
}
|
|
};
|
|
|
|
LoadingCache<Object, Object> cache =
|
|
CacheBuilder.newBuilder()
|
|
.recordStats()
|
|
.ticker(ticker)
|
|
.refreshAfterWrite(1, MILLISECONDS)
|
|
.build(loader);
|
|
Object key = new Object();
|
|
CacheStats stats = cache.stats();
|
|
assertEquals(0, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
assertSame(one, cache.getUnchecked(key));
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
ticker.advance(1, MILLISECONDS);
|
|
assertSame(one, cache.getUnchecked(key));
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(1, stats.hitCount());
|
|
|
|
ticker.advance(1, MILLISECONDS);
|
|
assertSame(one, cache.getUnchecked(key));
|
|
// refreshed
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(1, stats.loadExceptionCount());
|
|
assertEquals(2, stats.hitCount());
|
|
|
|
ticker.advance(1, MILLISECONDS);
|
|
assertSame(one, cache.getUnchecked(key));
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(1, stats.loadSuccessCount());
|
|
assertEquals(2, stats.loadExceptionCount());
|
|
assertEquals(3, stats.hitCount());
|
|
}
|
|
|
|
public void testBulkLoadUncheckedException() throws ExecutionException {
|
|
Exception e = new RuntimeException();
|
|
CacheLoader<Object, Object> loader = exceptionLoader(e);
|
|
LoadingCache<Object, Object> cache =
|
|
CacheBuilder.newBuilder().recordStats().build(bulkLoader(loader));
|
|
CacheStats stats = cache.stats();
|
|
assertEquals(0, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(0, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
|
|
try {
|
|
cache.getAll(asList(new Object()));
|
|
fail();
|
|
} catch (UncheckedExecutionException expected) {
|
|
assertThat(expected).hasCauseThat().isSameInstanceAs(e);
|
|
}
|
|
stats = cache.stats();
|
|
assertEquals(1, stats.missCount());
|
|
assertEquals(0, stats.loadSuccessCount());
|
|
assertEquals(1, stats.loadExceptionCount());
|
|
assertEquals(0, stats.hitCount());
|
|
}
|
|
|
|
public void testReloadAfterFailure() throws ExecutionException {
|
|
final AtomicInteger count = new AtomicInteger();
|
|
final Exception e = new IllegalStateException("exception to trigger failure on first load()");
|
|
CacheLoader<Integer, String> failOnceFunction =
|
|
new CacheLoader<Integer, String>() {
|
|
|
|
@Override
|
|
public String load(Integer key) throws Exception {
|
|
if (count.getAndIncrement() == 0) {
|
|
throw e;
|
|
}
|
|
return key.toString();
|
|
}
|
|
};
|
|
CountingRemovalListener<Integer, String> removalListener = countingRemovalListener();
|
|
LoadingCache<Integer, String> cache =
|
|
CacheBuilder.newBuilder().removalListener(removalListener).build(failOnceFunction);
|
|
|
|
try {
|
|
cache.getUnchecked(1);
|
|
fail();
|
|
} catch (UncheckedExecutionException ue) {
|
|
assertThat(ue).hasCauseThat().isSameInstanceAs(e);
|
|
}
|
|
|
|
assertEquals("1", cache.getUnchecked(1));
|
|
assertEquals(0, removalListener.getCount());
|
|
|
|
count.set(0);
|
|
cache.refresh(2);
|
|
checkLoggedCause(e);
|
|
|
|
assertEquals("2", cache.getUnchecked(2));
|
|
assertEquals(0, removalListener.getCount());
|
|
}
|
|
|
|
|
|
public void testReloadAfterValueReclamation() throws InterruptedException, ExecutionException {
|
|
CountingLoader countingLoader = new CountingLoader();
|
|
LoadingCache<Object, Object> cache =
|
|
CacheBuilder.newBuilder().weakValues().build(countingLoader);
|
|
ConcurrentMap<Object, Object> map = cache.asMap();
|
|
|
|
int iterations = 10;
|
|
WeakReference<Object> ref = new WeakReference<>(null);
|
|
int expectedComputations = 0;
|
|
for (int i = 0; i < iterations; i++) {
|
|
// The entry should get garbage collected and recomputed.
|
|
Object oldValue = ref.get();
|
|
if (oldValue == null) {
|
|
expectedComputations++;
|
|
}
|
|
ref = new WeakReference<>(cache.getUnchecked(1));
|
|
oldValue = null;
|
|
Thread.sleep(i);
|
|
System.gc();
|
|
}
|
|
assertEquals(expectedComputations, countingLoader.getCount());
|
|
|
|
for (int i = 0; i < iterations; i++) {
|
|
// The entry should get garbage collected and recomputed.
|
|
Object oldValue = ref.get();
|
|
if (oldValue == null) {
|
|
expectedComputations++;
|
|
}
|
|
cache.refresh(1);
|
|
checkNothingLogged();
|
|
ref = new WeakReference<>(map.get(1));
|
|
oldValue = null;
|
|
Thread.sleep(i);
|
|
System.gc();
|
|
}
|
|
assertEquals(expectedComputations, countingLoader.getCount());
|
|
}
|
|
|
|
public void testReloadAfterSimulatedValueReclamation() throws ExecutionException {
|
|
CountingLoader countingLoader = new CountingLoader();
|
|
LoadingCache<Object, Object> cache =
|
|
CacheBuilder.newBuilder().concurrencyLevel(1).weakValues().build(countingLoader);
|
|
|
|
Object key = new Object();
|
|
assertNotNull(cache.getUnchecked(key));
|
|
|
|
CacheTesting.simulateValueReclamation(cache, key);
|
|
|
|
// this blocks if computation can't deal with partially-collected values
|
|
assertNotNull(cache.getUnchecked(key));
|
|
assertEquals(1, cache.size());
|
|
assertEquals(2, countingLoader.getCount());
|
|
|
|
CacheTesting.simulateValueReclamation(cache, key);
|
|
cache.refresh(key);
|
|
checkNothingLogged();
|
|
assertEquals(1, cache.size());
|
|
assertEquals(3, countingLoader.getCount());
|
|
}
|
|
|
|
public void testReloadAfterSimulatedKeyReclamation() throws ExecutionException {
|
|
CountingLoader countingLoader = new CountingLoader();
|
|
LoadingCache<Object, Object> cache =
|
|
CacheBuilder.newBuilder().concurrencyLevel(1).weakKeys().build(countingLoader);
|
|
|
|
Object key = new Object();
|
|
assertNotNull(cache.getUnchecked(key));
|
|
assertEquals(1, cache.size());
|
|
|
|
CacheTesting.simulateKeyReclamation(cache, key);
|
|
|
|
// this blocks if computation can't deal with partially-collected values
|
|
assertNotNull(cache.getUnchecked(key));
|
|
assertEquals(2, countingLoader.getCount());
|
|
|
|
CacheTesting.simulateKeyReclamation(cache, key);
|
|
cache.refresh(key);
|
|
checkNothingLogged();
|
|
assertEquals(3, countingLoader.getCount());
|
|
}
|
|
|
|
/**
|
|
* Make sure LoadingCache correctly wraps ExecutionExceptions and UncheckedExecutionExceptions.
|
|
*/
|
|
public void testLoadingExceptionWithCause() {
|
|
final Exception cause = new Exception();
|
|
final UncheckedExecutionException uee = new UncheckedExecutionException(cause);
|
|
final ExecutionException ee = new ExecutionException(cause);
|
|
|
|
LoadingCache<Object, Object> cacheUnchecked =
|
|
CacheBuilder.newBuilder().build(exceptionLoader(uee));
|
|
LoadingCache<Object, Object> cacheChecked =
|
|
CacheBuilder.newBuilder().build(exceptionLoader(ee));
|
|
|
|
try {
|
|
cacheUnchecked.get(new Object());
|
|
fail();
|
|
} catch (ExecutionException e) {
|
|
fail();
|
|
} catch (UncheckedExecutionException caughtEe) {
|
|
assertThat(caughtEe).hasCauseThat().isSameInstanceAs(uee);
|
|
}
|
|
|
|
try {
|
|
cacheUnchecked.getUnchecked(new Object());
|
|
fail();
|
|
} catch (UncheckedExecutionException caughtUee) {
|
|
assertThat(caughtUee).hasCauseThat().isSameInstanceAs(uee);
|
|
}
|
|
|
|
cacheUnchecked.refresh(new Object());
|
|
checkLoggedCause(uee);
|
|
|
|
try {
|
|
cacheUnchecked.getAll(asList(new Object()));
|
|
fail();
|
|
} catch (ExecutionException e) {
|
|
fail();
|
|
} catch (UncheckedExecutionException caughtEe) {
|
|
assertThat(caughtEe).hasCauseThat().isSameInstanceAs(uee);
|
|
}
|
|
|
|
try {
|
|
cacheChecked.get(new Object());
|
|
fail();
|
|
} catch (ExecutionException caughtEe) {
|
|
assertThat(caughtEe).hasCauseThat().isSameInstanceAs(ee);
|
|
}
|
|
|
|
try {
|
|
cacheChecked.getUnchecked(new Object());
|
|
fail();
|
|
} catch (UncheckedExecutionException caughtUee) {
|
|
assertThat(caughtUee).hasCauseThat().isSameInstanceAs(ee);
|
|
}
|
|
|
|
cacheChecked.refresh(new Object());
|
|
checkLoggedCause(ee);
|
|
|
|
try {
|
|
cacheChecked.getAll(asList(new Object()));
|
|
fail();
|
|
} catch (ExecutionException caughtEe) {
|
|
assertThat(caughtEe).hasCauseThat().isSameInstanceAs(ee);
|
|
}
|
|
}
|
|
|
|
public void testBulkLoadingExceptionWithCause() {
|
|
final Exception cause = new Exception();
|
|
final UncheckedExecutionException uee = new UncheckedExecutionException(cause);
|
|
final ExecutionException ee = new ExecutionException(cause);
|
|
|
|
LoadingCache<Object, Object> cacheUnchecked =
|
|
CacheBuilder.newBuilder().build(bulkLoader(exceptionLoader(uee)));
|
|
LoadingCache<Object, Object> cacheChecked =
|
|
CacheBuilder.newBuilder().build(bulkLoader(exceptionLoader(ee)));
|
|
|
|
try {
|
|
cacheUnchecked.getAll(asList(new Object()));
|
|
fail();
|
|
} catch (ExecutionException e) {
|
|
fail();
|
|
} catch (UncheckedExecutionException caughtEe) {
|
|
assertThat(caughtEe).hasCauseThat().isSameInstanceAs(uee);
|
|
}
|
|
|
|
try {
|
|
cacheChecked.getAll(asList(new Object()));
|
|
fail();
|
|
} catch (ExecutionException caughtEe) {
|
|
assertThat(caughtEe).hasCauseThat().isSameInstanceAs(ee);
|
|
}
|
|
}
|
|
|
|
|
|
public void testConcurrentLoading() throws InterruptedException {
|
|
testConcurrentLoading(CacheBuilder.newBuilder());
|
|
}
|
|
|
|
private static void testConcurrentLoading(CacheBuilder<Object, Object> builder)
|
|
throws InterruptedException {
|
|
testConcurrentLoadingDefault(builder);
|
|
testConcurrentLoadingNull(builder);
|
|
testConcurrentLoadingUncheckedException(builder);
|
|
testConcurrentLoadingCheckedException(builder);
|
|
}
|
|
|
|
|
|
public void testConcurrentExpirationLoading() throws InterruptedException {
|
|
testConcurrentLoading(CacheBuilder.newBuilder().expireAfterWrite(10, TimeUnit.SECONDS));
|
|
}
|
|
|
|
/**
|
|
* On a successful concurrent computation, only one thread does the work, but all the threads get
|
|
* the same result.
|
|
*/
|
|
private static void testConcurrentLoadingDefault(CacheBuilder<Object, Object> builder)
|
|
throws InterruptedException {
|
|
|
|
int count = 10;
|
|
final AtomicInteger callCount = new AtomicInteger();
|
|
final CountDownLatch startSignal = new CountDownLatch(count + 1);
|
|
final Object result = new Object();
|
|
|
|
LoadingCache<String, Object> cache =
|
|
builder.build(
|
|
new CacheLoader<String, Object>() {
|
|
@Override
|
|
public Object load(String key) throws InterruptedException {
|
|
callCount.incrementAndGet();
|
|
startSignal.await();
|
|
return result;
|
|
}
|
|
});
|
|
|
|
List<Object> resultArray = doConcurrentGet(cache, "bar", count, startSignal);
|
|
|
|
assertEquals(1, callCount.get());
|
|
for (int i = 0; i < count; i++) {
|
|
assertSame("result(" + i + ") didn't match expected", result, resultArray.get(i));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* On a concurrent computation that returns null, all threads should get an
|
|
* InvalidCacheLoadException, with the loader only called once. The result should not be cached (a
|
|
* later request should call the loader again).
|
|
*/
|
|
private static void testConcurrentLoadingNull(CacheBuilder<Object, Object> builder)
|
|
throws InterruptedException {
|
|
|
|
int count = 10;
|
|
final AtomicInteger callCount = new AtomicInteger();
|
|
final CountDownLatch startSignal = new CountDownLatch(count + 1);
|
|
|
|
LoadingCache<String, String> cache =
|
|
builder.build(
|
|
new CacheLoader<String, String>() {
|
|
@Override
|
|
public String load(String key) throws InterruptedException {
|
|
callCount.incrementAndGet();
|
|
startSignal.await();
|
|
return null;
|
|
}
|
|
});
|
|
|
|
List<Object> result = doConcurrentGet(cache, "bar", count, startSignal);
|
|
|
|
assertEquals(1, callCount.get());
|
|
for (int i = 0; i < count; i++) {
|
|
assertThat(result.get(i)).isInstanceOf(InvalidCacheLoadException.class);
|
|
}
|
|
|
|
// subsequent calls should call the loader again, not get the old exception
|
|
try {
|
|
cache.getUnchecked("bar");
|
|
fail();
|
|
} catch (InvalidCacheLoadException expected) {
|
|
}
|
|
assertEquals(2, callCount.get());
|
|
}
|
|
|
|
/**
|
|
* On a concurrent computation that throws an unchecked exception, all threads should get the
|
|
* (wrapped) exception, with the loader called only once. The result should not be cached (a later
|
|
* request should call the loader again).
|
|
*/
|
|
private static void testConcurrentLoadingUncheckedException(CacheBuilder<Object, Object> builder)
|
|
throws InterruptedException {
|
|
|
|
int count = 10;
|
|
final AtomicInteger callCount = new AtomicInteger();
|
|
final CountDownLatch startSignal = new CountDownLatch(count + 1);
|
|
final RuntimeException e = new RuntimeException();
|
|
|
|
LoadingCache<String, String> cache =
|
|
builder.build(
|
|
new CacheLoader<String, String>() {
|
|
@Override
|
|
public String load(String key) throws InterruptedException {
|
|
callCount.incrementAndGet();
|
|
startSignal.await();
|
|
throw e;
|
|
}
|
|
});
|
|
|
|
List<Object> result = doConcurrentGet(cache, "bar", count, startSignal);
|
|
|
|
assertEquals(1, callCount.get());
|
|
for (int i = 0; i < count; i++) {
|
|
// doConcurrentGet alternates between calling getUnchecked and calling get, but an unchecked
|
|
// exception thrown by the loader is always wrapped as an UncheckedExecutionException.
|
|
assertThat(result.get(i)).isInstanceOf(UncheckedExecutionException.class);
|
|
assertThat(((UncheckedExecutionException) result.get(i))).hasCauseThat().isSameInstanceAs(e);
|
|
}
|
|
|
|
// subsequent calls should call the loader again, not get the old exception
|
|
try {
|
|
cache.getUnchecked("bar");
|
|
fail();
|
|
} catch (UncheckedExecutionException expected) {
|
|
}
|
|
assertEquals(2, callCount.get());
|
|
}
|
|
|
|
/**
|
|
* On a concurrent computation that throws a checked exception, all threads should get the
|
|
* (wrapped) exception, with the loader called only once. The result should not be cached (a later
|
|
* request should call the loader again).
|
|
*/
|
|
private static void testConcurrentLoadingCheckedException(CacheBuilder<Object, Object> builder)
|
|
throws InterruptedException {
|
|
|
|
int count = 10;
|
|
final AtomicInteger callCount = new AtomicInteger();
|
|
final CountDownLatch startSignal = new CountDownLatch(count + 1);
|
|
final IOException e = new IOException();
|
|
|
|
LoadingCache<String, String> cache =
|
|
builder.build(
|
|
new CacheLoader<String, String>() {
|
|
@Override
|
|
public String load(String key) throws IOException, InterruptedException {
|
|
callCount.incrementAndGet();
|
|
startSignal.await();
|
|
throw e;
|
|
}
|
|
});
|
|
|
|
List<Object> result = doConcurrentGet(cache, "bar", count, startSignal);
|
|
|
|
assertEquals(1, callCount.get());
|
|
for (int i = 0; i < count; i++) {
|
|
// doConcurrentGet alternates between calling getUnchecked and calling get. If we call get(),
|
|
// we should get an ExecutionException; if we call getUnchecked(), we should get an
|
|
// UncheckedExecutionException.
|
|
int mod = i % 3;
|
|
if (mod == 0 || mod == 2) {
|
|
assertThat(result.get(i)).isInstanceOf(ExecutionException.class);
|
|
assertThat((ExecutionException) result.get(i)).hasCauseThat().isSameInstanceAs(e);
|
|
} else {
|
|
assertThat(result.get(i)).isInstanceOf(UncheckedExecutionException.class);
|
|
assertThat((UncheckedExecutionException) result.get(i)).hasCauseThat().isSameInstanceAs(e);
|
|
}
|
|
}
|
|
|
|
// subsequent calls should call the loader again, not get the old exception
|
|
try {
|
|
cache.getUnchecked("bar");
|
|
fail();
|
|
} catch (UncheckedExecutionException expected) {
|
|
}
|
|
assertEquals(2, callCount.get());
|
|
}
|
|
|
|
/**
|
|
* Test-helper method that performs {@code nThreads} concurrent calls to {@code cache.get(key)} or
|
|
* {@code cache.getUnchecked(key)}, and returns a List containing each of the results. The result
|
|
* for any given call to {@code cache.get} or {@code cache.getUnchecked} is the value returned, or
|
|
* the exception thrown.
|
|
*
|
|
* <p>As we iterate from {@code 0} to {@code nThreads}, threads with an even index will call
|
|
* {@code getUnchecked}, and threads with an odd index will call {@code get}. If the cache throws
|
|
* exceptions, this difference may be visible in the returned List.
|
|
*/
|
|
private static <K> List<Object> doConcurrentGet(
|
|
final LoadingCache<K, ?> cache,
|
|
final K key,
|
|
int nThreads,
|
|
final CountDownLatch gettersStartedSignal)
|
|
throws InterruptedException {
|
|
|
|
final AtomicReferenceArray<Object> result = new AtomicReferenceArray<>(nThreads);
|
|
final CountDownLatch gettersComplete = new CountDownLatch(nThreads);
|
|
for (int i = 0; i < nThreads; i++) {
|
|
final int index = i;
|
|
Thread thread =
|
|
new Thread(
|
|
new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
gettersStartedSignal.countDown();
|
|
Object value = null;
|
|
try {
|
|
int mod = index % 3;
|
|
if (mod == 0) {
|
|
value = cache.get(key);
|
|
} else if (mod == 1) {
|
|
value = cache.getUnchecked(key);
|
|
} else {
|
|
cache.refresh(key);
|
|
value = cache.get(key);
|
|
}
|
|
result.set(index, value);
|
|
} catch (Throwable t) {
|
|
result.set(index, t);
|
|
}
|
|
gettersComplete.countDown();
|
|
}
|
|
});
|
|
thread.start();
|
|
// we want to wait until each thread is WAITING - one thread waiting inside CacheLoader.load
|
|
// (in startSignal.await()), and the others waiting for that thread's result.
|
|
while (thread.isAlive() && thread.getState() != Thread.State.WAITING) {
|
|
Thread.yield();
|
|
}
|
|
}
|
|
gettersStartedSignal.countDown();
|
|
gettersComplete.await();
|
|
|
|
List<Object> resultList = Lists.newArrayListWithExpectedSize(nThreads);
|
|
for (int i = 0; i < nThreads; i++) {
|
|
resultList.add(result.get(i));
|
|
}
|
|
return resultList;
|
|
}
|
|
|
|
|
|
public void testAsMapDuringLoading() throws InterruptedException, ExecutionException {
|
|
final CountDownLatch getStartedSignal = new CountDownLatch(2);
|
|
final CountDownLatch letGetFinishSignal = new CountDownLatch(1);
|
|
final CountDownLatch getFinishedSignal = new CountDownLatch(2);
|
|
final String getKey = "get";
|
|
final String refreshKey = "refresh";
|
|
final String suffix = "Suffix";
|
|
|
|
CacheLoader<String, String> computeFunction =
|
|
new CacheLoader<String, String>() {
|
|
@Override
|
|
public String load(String key) throws InterruptedException {
|
|
getStartedSignal.countDown();
|
|
letGetFinishSignal.await();
|
|
return key + suffix;
|
|
}
|
|
};
|
|
|
|
final LoadingCache<String, String> cache = CacheBuilder.newBuilder().build(computeFunction);
|
|
ConcurrentMap<String, String> map = cache.asMap();
|
|
map.put(refreshKey, refreshKey);
|
|
assertEquals(1, map.size());
|
|
assertFalse(map.containsKey(getKey));
|
|
assertSame(refreshKey, map.get(refreshKey));
|
|
|
|
new Thread() {
|
|
@Override
|
|
public void run() {
|
|
cache.getUnchecked(getKey);
|
|
getFinishedSignal.countDown();
|
|
}
|
|
}.start();
|
|
new Thread() {
|
|
@Override
|
|
public void run() {
|
|
cache.refresh(refreshKey);
|
|
getFinishedSignal.countDown();
|
|
}
|
|
}.start();
|
|
|
|
getStartedSignal.await();
|
|
|
|
// computation is in progress; asMap shouldn't have changed
|
|
assertEquals(1, map.size());
|
|
assertFalse(map.containsKey(getKey));
|
|
assertSame(refreshKey, map.get(refreshKey));
|
|
|
|
// let computation complete
|
|
letGetFinishSignal.countDown();
|
|
getFinishedSignal.await();
|
|
checkNothingLogged();
|
|
|
|
// asMap view should have been updated
|
|
assertEquals(2, cache.size());
|
|
assertEquals(getKey + suffix, map.get(getKey));
|
|
assertEquals(refreshKey + suffix, map.get(refreshKey));
|
|
}
|
|
|
|
|
|
public void testInvalidateDuringLoading() throws InterruptedException, ExecutionException {
|
|
// computation starts; invalidate() is called on the key being computed, computation finishes
|
|
final CountDownLatch computationStarted = new CountDownLatch(2);
|
|
final CountDownLatch letGetFinishSignal = new CountDownLatch(1);
|
|
final CountDownLatch getFinishedSignal = new CountDownLatch(2);
|
|
final String getKey = "get";
|
|
final String refreshKey = "refresh";
|
|
final String suffix = "Suffix";
|
|
|
|
CacheLoader<String, String> computeFunction =
|
|
new CacheLoader<String, String>() {
|
|
@Override
|
|
public String load(String key) throws InterruptedException {
|
|
computationStarted.countDown();
|
|
letGetFinishSignal.await();
|
|
return key + suffix;
|
|
}
|
|
};
|
|
|
|
final LoadingCache<String, String> cache = CacheBuilder.newBuilder().build(computeFunction);
|
|
ConcurrentMap<String, String> map = cache.asMap();
|
|
map.put(refreshKey, refreshKey);
|
|
|
|
new Thread() {
|
|
@Override
|
|
public void run() {
|
|
cache.getUnchecked(getKey);
|
|
getFinishedSignal.countDown();
|
|
}
|
|
}.start();
|
|
new Thread() {
|
|
@Override
|
|
public void run() {
|
|
cache.refresh(refreshKey);
|
|
getFinishedSignal.countDown();
|
|
}
|
|
}.start();
|
|
|
|
computationStarted.await();
|
|
cache.invalidate(getKey);
|
|
cache.invalidate(refreshKey);
|
|
assertFalse(map.containsKey(getKey));
|
|
assertFalse(map.containsKey(refreshKey));
|
|
|
|
// let computation complete
|
|
letGetFinishSignal.countDown();
|
|
getFinishedSignal.await();
|
|
checkNothingLogged();
|
|
|
|
// results should be visible
|
|
assertEquals(2, cache.size());
|
|
assertEquals(getKey + suffix, map.get(getKey));
|
|
assertEquals(refreshKey + suffix, map.get(refreshKey));
|
|
assertEquals(2, cache.size());
|
|
}
|
|
|
|
|
|
public void testInvalidateAndReloadDuringLoading()
|
|
throws InterruptedException, ExecutionException {
|
|
// computation starts; clear() is called, computation finishes
|
|
final CountDownLatch computationStarted = new CountDownLatch(2);
|
|
final CountDownLatch letGetFinishSignal = new CountDownLatch(1);
|
|
final CountDownLatch getFinishedSignal = new CountDownLatch(4);
|
|
final String getKey = "get";
|
|
final String refreshKey = "refresh";
|
|
final String suffix = "Suffix";
|
|
|
|
CacheLoader<String, String> computeFunction =
|
|
new CacheLoader<String, String>() {
|
|
@Override
|
|
public String load(String key) throws InterruptedException {
|
|
computationStarted.countDown();
|
|
letGetFinishSignal.await();
|
|
return key + suffix;
|
|
}
|
|
};
|
|
|
|
final LoadingCache<String, String> cache = CacheBuilder.newBuilder().build(computeFunction);
|
|
ConcurrentMap<String, String> map = cache.asMap();
|
|
map.put(refreshKey, refreshKey);
|
|
|
|
new Thread() {
|
|
@Override
|
|
public void run() {
|
|
cache.getUnchecked(getKey);
|
|
getFinishedSignal.countDown();
|
|
}
|
|
}.start();
|
|
new Thread() {
|
|
@Override
|
|
public void run() {
|
|
cache.refresh(refreshKey);
|
|
getFinishedSignal.countDown();
|
|
}
|
|
}.start();
|
|
|
|
computationStarted.await();
|
|
cache.invalidate(getKey);
|
|
cache.invalidate(refreshKey);
|
|
assertFalse(map.containsKey(getKey));
|
|
assertFalse(map.containsKey(refreshKey));
|
|
|
|
// start new computations
|
|
new Thread() {
|
|
@Override
|
|
public void run() {
|
|
cache.getUnchecked(getKey);
|
|
getFinishedSignal.countDown();
|
|
}
|
|
}.start();
|
|
new Thread() {
|
|
@Override
|
|
public void run() {
|
|
cache.refresh(refreshKey);
|
|
getFinishedSignal.countDown();
|
|
}
|
|
}.start();
|
|
|
|
// let computation complete
|
|
letGetFinishSignal.countDown();
|
|
getFinishedSignal.await();
|
|
checkNothingLogged();
|
|
|
|
// results should be visible
|
|
assertEquals(2, cache.size());
|
|
assertEquals(getKey + suffix, map.get(getKey));
|
|
assertEquals(refreshKey + suffix, map.get(refreshKey));
|
|
}
|
|
|
|
|
|
public void testExpandDuringLoading() throws InterruptedException {
|
|
final int count = 3;
|
|
final AtomicInteger callCount = new AtomicInteger();
|
|
// tells the computing thread when to start computing
|
|
final CountDownLatch computeSignal = new CountDownLatch(1);
|
|
// tells the main thread when computation is pending
|
|
final CountDownLatch secondSignal = new CountDownLatch(1);
|
|
// tells the main thread when the second get has started
|
|
final CountDownLatch thirdSignal = new CountDownLatch(1);
|
|
// tells the main thread when the third get has started
|
|
final CountDownLatch fourthSignal = new CountDownLatch(1);
|
|
// tells the test when all gets have returned
|
|
final CountDownLatch doneSignal = new CountDownLatch(count);
|
|
|
|
CacheLoader<String, String> computeFunction =
|
|
new CacheLoader<String, String>() {
|
|
@Override
|
|
public String load(String key) throws InterruptedException {
|
|
callCount.incrementAndGet();
|
|
secondSignal.countDown();
|
|
computeSignal.await();
|
|
return key + "foo";
|
|
}
|
|
};
|
|
|
|
final LoadingCache<String, String> cache =
|
|
CacheBuilder.newBuilder().weakKeys().build(computeFunction);
|
|
|
|
final AtomicReferenceArray<String> result = new AtomicReferenceArray<>(count);
|
|
|
|
final String key = "bar";
|
|
|
|
// start computing thread
|
|
new Thread() {
|
|
@Override
|
|
public void run() {
|
|
result.set(0, cache.getUnchecked(key));
|
|
doneSignal.countDown();
|
|
}
|
|
}.start();
|
|
|
|
// wait for computation to start
|
|
secondSignal.await();
|
|
|
|
// start waiting thread
|
|
new Thread() {
|
|
@Override
|
|
public void run() {
|
|
thirdSignal.countDown();
|
|
result.set(1, cache.getUnchecked(key));
|
|
doneSignal.countDown();
|
|
}
|
|
}.start();
|
|
|
|
// give the second get a chance to run; it is okay for this to be racy
|
|
// as the end result should be the same either way
|
|
thirdSignal.await();
|
|
Thread.yield();
|
|
|
|
// Expand!
|
|
CacheTesting.forceExpandSegment(cache, key);
|
|
|
|
// start another waiting thread
|
|
new Thread() {
|
|
@Override
|
|
public void run() {
|
|
fourthSignal.countDown();
|
|
result.set(2, cache.getUnchecked(key));
|
|
doneSignal.countDown();
|
|
}
|
|
}.start();
|
|
|
|
// give the third get a chance to run; it is okay for this to be racy
|
|
// as the end result should be the same either way
|
|
fourthSignal.await();
|
|
Thread.yield();
|
|
|
|
// let computation finish
|
|
computeSignal.countDown();
|
|
doneSignal.await();
|
|
|
|
assertTrue(callCount.get() == 1);
|
|
assertEquals("barfoo", result.get(0));
|
|
assertEquals("barfoo", result.get(1));
|
|
assertEquals("barfoo", result.get(2));
|
|
assertEquals("barfoo", cache.getUnchecked(key));
|
|
}
|
|
|
|
// Test ignored because it is extremely flaky in CI builds
|
|
|
|
public void
|
|
ignoreTestExpandDuringRefresh()
|
|
throws InterruptedException, ExecutionException {
|
|
final AtomicInteger callCount = new AtomicInteger();
|
|
// tells the computing thread when to start computing
|
|
final CountDownLatch computeSignal = new CountDownLatch(1);
|
|
// tells the main thread when computation is pending
|
|
final CountDownLatch secondSignal = new CountDownLatch(1);
|
|
// tells the main thread when the second get has started
|
|
final CountDownLatch thirdSignal = new CountDownLatch(1);
|
|
// tells the main thread when the third get has started
|
|
final CountDownLatch fourthSignal = new CountDownLatch(1);
|
|
// tells the test when all gets have returned
|
|
final CountDownLatch doneSignal = new CountDownLatch(3);
|
|
final String suffix = "Suffix";
|
|
|
|
CacheLoader<String, String> computeFunction =
|
|
new CacheLoader<String, String>() {
|
|
@Override
|
|
public String load(String key) throws InterruptedException {
|
|
callCount.incrementAndGet();
|
|
secondSignal.countDown();
|
|
computeSignal.await();
|
|
return key + suffix;
|
|
}
|
|
};
|
|
|
|
final AtomicReferenceArray<String> result = new AtomicReferenceArray<>(2);
|
|
|
|
final LoadingCache<String, String> cache = CacheBuilder.newBuilder().build(computeFunction);
|
|
final String key = "bar";
|
|
cache.asMap().put(key, key);
|
|
|
|
// start computing thread
|
|
new Thread() {
|
|
@Override
|
|
public void run() {
|
|
cache.refresh(key);
|
|
doneSignal.countDown();
|
|
}
|
|
}.start();
|
|
|
|
// wait for computation to start
|
|
secondSignal.await();
|
|
checkNothingLogged();
|
|
|
|
// start waiting thread
|
|
new Thread() {
|
|
@Override
|
|
public void run() {
|
|
thirdSignal.countDown();
|
|
result.set(0, cache.getUnchecked(key));
|
|
doneSignal.countDown();
|
|
}
|
|
}.start();
|
|
|
|
// give the second get a chance to run; it is okay for this to be racy
|
|
// as the end result should be the same either way
|
|
thirdSignal.await();
|
|
Thread.yield();
|
|
|
|
// Expand!
|
|
CacheTesting.forceExpandSegment(cache, key);
|
|
|
|
// start another waiting thread
|
|
new Thread() {
|
|
@Override
|
|
public void run() {
|
|
fourthSignal.countDown();
|
|
result.set(1, cache.getUnchecked(key));
|
|
doneSignal.countDown();
|
|
}
|
|
}.start();
|
|
|
|
// give the third get a chance to run; it is okay for this to be racy
|
|
// as the end result should be the same either way
|
|
fourthSignal.await();
|
|
Thread.yield();
|
|
|
|
// let computation finish
|
|
computeSignal.countDown();
|
|
doneSignal.await();
|
|
|
|
assertTrue(callCount.get() == 1);
|
|
assertEquals(key, result.get(0));
|
|
assertEquals(key, result.get(1));
|
|
assertEquals(key + suffix, cache.getUnchecked(key));
|
|
}
|
|
|
|
static <T> Callable<T> throwing(final Exception exception) {
|
|
return new Callable<T>() {
|
|
@Override
|
|
public T call() throws Exception {
|
|
throw exception;
|
|
}
|
|
};
|
|
}
|
|
}
|