I'm getting results that I don't really understand while using Guava Caches.
I am implementing a single key cache that I want to refresh asynchronously.
I hit the cache every second and I have set refreshAfterWrite to 20 seconds. My load/reload function takes 5 seconds.
If I print out at the start of the load/reload method the current time - I would expect some results like this:
load call started at 00:00:00
reload call started at 00:00:25
reload call started at 00:00:50
So the load would take 5 seconds and the next write would trigger 20 seconds after that (5+20=25). That write would occur at 50 seconds (25 + 5 + 20 = 50) seconds after that.. etc
Instead I get:
load call started at 00:00:00
reload call started at 00:00:25
reload call started at 00:00:30
This suggests that the second reload occurs straight after the first reload has finished processing.
I thought that the write would occur after the future has been processed and so the next reload would be scheduled for 20 seconds after that?
Have I found a bug or do I have a fundamental misunderstanding of how refreshAfterWrite works?
Sample code is below:
private static SimpleDateFormat format = new SimpleDateFormat("hh:mm:ss");
public static void main(String[] args) throws ExecutionException, InterruptedException {
final ExecutorService executor = Executors.newFixedThreadPool(3);
final LoadingCache<String, Long> cache = CacheBuilder.newBuilder().maximumSize(1) //
.refreshAfterWrite(20, TimeUnit.SECONDS)//
.build(new CacheLoader<String, Long>() {//
public Long load(String key) {
return getLongRunningProcess("load", key);
}
public ListenableFuture<Long> reload(final String key, Long prevGraph) {
ListenableFutureTask<Long> task = ListenableFutureTask.create(new Callable<Long>() {
public Long call() {
return getLongRunningProcess("reload", key);
}
});
executor.execute(task);
return task;
}
});
while (true) {
Thread.sleep(1000L);
cache.get(CACHE_KEY);
}
}
private static Long getLongRunningProcess(String callType, String key) {
System.out.printf("%s call started at %s\n", callType, format.format(new Date()));
try {
Thread.sleep(5000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
return counter.getAndIncrement();
}
}
Guava's cache is built on top of Java 5's ConcurrentHashMap with a default concurrency level of 4. This setting is because that hash table is segmented into multiple smaller tables, so more segments allows for higher concurrency at a cost of a larger memory footprint.
Values are automatically loaded by the cache, and are stored in the cache until either evicted or manually invalidated. Implementations of this interface are expected to be thread-safe, and can be safely accessed by multiple concurrent threads.
A non-blocking cache implementation. Guava java library has an interface LoadingCache that has methods related with cache. The library also provides a CacheBuilder whose constructor needs a CacheLoader that has different methods to load values into the cache.
I think you've found a legit bug. (I help maintain common.cache
.)
If I'm following things correctly, I believe the chain of events is as follows:
Let's say get A is the first get
that causes a refresh, and get B is the first get
after that.
scheduleRefresh
, which starts the refresh
task in the executor. The entry value reference is replaced with a LoadingValueReference
, and loadAsync
adds a listener waiting for the reload to complete.scheduleRefresh
. The access time has not been updated yet, so it proceeds, and goes into insertLoadingValueReference
.StrongValueReference
, since the load is complete. The lock is released.(Update: filed https://code.google.com/p/guava-libraries/issues/detail?id=1211.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With