-
Notifications
You must be signed in to change notification settings - Fork 11.1k
Description
Original issue created by harmathdenes on 2013-03-14 at 05:34 PM
When using a LoadingCache with refreshAfterWrite, if the given CacheLoader.reload() is asynchronous and returns too fast, then the following scenario occurs indeterministically:
0. The value already exist in the cache but is eligible for refresh.
- A call to get() starts a refresh, the asynchronous reload() returns a future that is already done when Cache checks this and therefore returns synchronously with the result.
1a. Meanwhile the future's callback starts to put the new value into the cache. - An immediate second call to get() occurs before 1a. would complete, therefore returns the value described in 0.
The following test case reproduces the problem after some iterations:
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Assert;
import org.junit.Test;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
public class TooFastRefresh {
private ListeningExecutorService EXECUTOR = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
@Test
public void test() throws Exception {
final AtomicInteger counter = new AtomicInteger();
LoadingCache<String, Integer> cache = CacheBuilder.newBuilder()
.refreshAfterWrite(10, TimeUnit.MILLISECONDS)
.build(new CacheLoader<String, Integer>() {
@Override
public Integer load(String key) {
return counter.incrementAndGet();
}
@Override
public ListenableFuture<Integer> reload(final String key, Integer prev) {
return EXECUTOR.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return load(key);
}
});
}
});
cache.get("");
for(int i=0; i<50; i++) {
Thread.sleep(20);
Integer first = cache.get("");
Integer second = cache.get("");
Assert.assertEquals(first, second);
}
EXECUTOR.shutdown();
}
}