/* * 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 java.util.Arrays.asList; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.junit.contrib.truth.Truth.ASSERT; 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 junit.framework.TestCase; import java.io.IOException; import java.lang.ref.WeakReference; import java.util.List; import java.util.Map; 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; /** * 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(); 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() { assertTrue(logHandler.getStoredLogRecords().isEmpty()); } private void checkLoggedCause(Throwable t) { assertSame(t, popLoggedThrowable().getCause()); } private void checkLoggedInvalidLoad() { assertTrue(popLoggedThrowable() instanceof InvalidCacheLoadException); } 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<? extends Object> 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)); ASSERT.that(result.keySet()).hasContentsAnyOrder(lookupKeys); for (Map.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<? extends Object> 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)); ASSERT.that(result.keySet()).hasContentsAnyOrder(lookupKeys); for (Map.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<? extends Object> 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<? extends Object> 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<? extends Object> 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<? extends Object> 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) { assertSame(e, expected.getCause()); } 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) { assertSame(e, expected.getCause()); } 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) { assertSame(callableError, expected.getCause()); } 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) { assertSame(e, expected.getCause()); } 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) { assertSame(e, expected.getCause()); } 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) { assertSame(e, expected.getCause()); } 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) { assertSame(e, expected.getCause()); } 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) { assertSame(callableException, expected.getCause()); } 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) { assertSame(e, expected.getCause()); } 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) { assertSame(e, expected.getCause()); } 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) { assertSame(e, expected.getCause()); } 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) { assertSame(e, expected.getCause()); } 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) { assertSame(callableException, expected.getCause()); } 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) { assertSame(e, expected.getCause()); } 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) { assertSame(e, expected.getCause()); } 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) { assertSame(e, ue.getCause()); } 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<Object>(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<Object>(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<Object>(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) { assertSame(uee, caughtEe.getCause()); } try { cacheUnchecked.getUnchecked(new Object()); fail(); } catch (UncheckedExecutionException caughtUee) { assertSame(uee, caughtUee.getCause()); } cacheUnchecked.refresh(new Object()); checkLoggedCause(uee); try { cacheUnchecked.getAll(asList(new Object())); fail(); } catch (ExecutionException e) { fail(); } catch (UncheckedExecutionException caughtEe) { assertSame(uee, caughtEe.getCause()); } try { cacheChecked.get(new Object()); fail(); } catch (ExecutionException caughtEe) { assertSame(ee, caughtEe.getCause()); } try { cacheChecked.getUnchecked(new Object()); fail(); } catch (UncheckedExecutionException caughtUee) { assertSame(ee, caughtUee.getCause()); } cacheChecked.refresh(new Object()); checkLoggedCause(ee); try { cacheChecked.getAll(asList(new Object())); fail(); } catch (ExecutionException caughtEe) { assertSame(ee, caughtEe.getCause()); } } 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) { assertSame(uee, caughtEe.getCause()); } try { cacheChecked.getAll(asList(new Object())); fail(); } catch (ExecutionException caughtEe) { assertSame(ee, caughtEe.getCause()); } } public void testConcurrentLoading() throws InterruptedException { testConcurrentLoading(CacheBuilder.newBuilder()); } public void testConcurrentExpirationLoading() throws InterruptedException { testConcurrentLoading(CacheBuilder.newBuilder().expireAfterWrite(10, TimeUnit.SECONDS)); } private static void testConcurrentLoading(CacheBuilder<Object, Object> builder) throws InterruptedException { testConcurrentLoadingDefault(builder); testConcurrentLoadingNull(builder); testConcurrentLoadingUncheckedException(builder); testConcurrentLoadingCheckedException(builder); } /** * 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++) { assertTrue(result.get(i) instanceof InvalidCacheLoadException); } // 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. assertTrue(result.get(i) instanceof UncheckedExecutionException); assertSame(e, ((UncheckedExecutionException) result.get(i)).getCause()); } // 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) { assertTrue(result.get(i) instanceof ExecutionException); assertSame(e, ((ExecutionException) result.get(i)).getCause()); } else { assertTrue(result.get(i) instanceof UncheckedExecutionException); assertSame(e, ((UncheckedExecutionException) result.get(i)).getCause()); } } // 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<Object>(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<String>(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)); } public void testExpandDuringRefresh() 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<String>(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; } }; } }