LoadingCache stops loading forever after race condition
See original GitHub issueThe following code demonstrates how a LoadingCache
stops loading forever if invalidateAll()
is called during a load operation.
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import com.google.common.base.Preconditions;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
public class CacheBug {
private static final ExecutorService EXECUTOR = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setDaemon(true).build());
static final LoadingCache<String, Long> THE_CACHE = CacheBuilder.newBuilder()
.refreshAfterWrite(1, TimeUnit.MILLISECONDS)
.build(new CacheLoader<String, Long>() {
@Override
public Long load(String s) throws InterruptedException {
Thread.sleep(200);
return System.currentTimeMillis();
}
});
public static void main(String[] args) throws ExecutionException, InterruptedException {
THE_CACHE.getUnchecked("");
Thread.sleep(100);
EXECUTOR.submit(() -> THE_CACHE.get(""));
for (int i = 0; i < 10; i++) {
Thread.sleep(100);
THE_CACHE.invalidateAll();
System.err.println("Current time is: " + THE_CACHE.get(""));
}
Preconditions.checkArgument(System.currentTimeMillis() - THE_CACHE.get("") < 500);
}
}
Output:
Current time is: 1508359149314
Current time is: 1508359149217
Current time is: 1508359149217
Current time is: 1508359149217
Current time is: 1508359149217
Current time is: 1508359149217
Current time is: 1508359149217
Current time is: 1508359149217
Current time is: 1508359149217
Current time is: 1508359149217
Exception in thread "main" java.lang.IllegalArgumentException
at com.google.common.base.Preconditions.checkArgument(Preconditions.java:120)
at CacheBug.main(CacheBug.java:34)
The expected behaviour is that the time should increase (by about 300ms) in every iteration.
Issue Analytics
- State:
- Created 6 years ago
- Comments:11 (8 by maintainers)
Top Results From Across the Web
LoadingCache stop auto-refreshing and only serves stale data
It seems that for some reason LoadingCache is not seeing that the data is much older than 5 minutes. After I restart the...
Read more >Race condition when caching using the get-compute-put pattern
As you can see, the invalidate operation is invoked right away, despite an ongoing cache load for the k key. Luckily, not all...
Read more >Co-op Career - Stuck on loading screen (Waiting for players or ...
Hello, I have problem with stuck in loading screen. ... After the race, we turned off the game and then we came back...
Read more >Confluent Platform Component Changelogs
Confluent Platform Docker Images will no longer ship with Yum/Apt configuration that allowed users to update running Confluent Platform containers to the next ......
Read more >Hadoop 2.4.1 Release Notes
Run yarn application -list -appStates FAILED, It does not print http protocol name like ... Race condition in failover can cause RetryCache fail...
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
@lowasser So roughly speaking, that’s what happens:
summarizing that, if I didn’t make a mistake, we clearly could see, that while value is loading by
pool-1-thread-1
we’re doinginvalidateAll
, which causes us to set upthis.count = 0
. Thenmain
wakes up and tries to load a value, as underlying entry in a table is empty and shall be initialized (then sleeps for about200 ms
).poll-1-thread-1
wakes up, sets up it’s value, but, what it does here is it checks, if there’s already a bucket and entry initialized (which is generously done bymain
), however, a value in an entry is still loading.pool-1-thread-1
substitutes the value with it’s own loaded one and doesn’t affectthis.count
which is0
(it actually does--newCount
and stores it) at the moment.main
apparently wakes up and tries to set up it’s value, however fails to pass the condition:and thus doesn’t update a value along with
this.count
which is still equals to zero. Thusmain
publishesREPLACED
and exits.All the subsequent loads fail miserably as
main
, and all theinvalidateAll
attempts fail because ofthis.count == 0
.In my opinion there’s two ways to fix this issue:
1st if kinda safe and quickest one, however not the best one, as I think. It is just to delete a condition:
at
LocalCache.Segment#clear:3357
which preventsinvalidateAll
from clearing kinda broken segment.or the second one is to update
LocalCache.Segment#storeLoadedValue:3284
and add here something like follows, to catch the case, whenoldValueReference.isLoading == false && newValueReference.isLoading == true
and we still want to update a value and incrementthis.count
.I must admit, that I like 2nd option (not exactly that implementation but I like the approach) much more because in my opinion it fixes the root cause (we fail to update a value in a bucket), however it breaks the test
LocalCacheTest.testSegmentStoreComputedValue
for package-privatestoreLoadedValue
for the clobbered case. However 1st option is quite safe (I don’t take in account performance drawback which caused byinvalidateAll
being clearing an internal table in cause whencount
should be0
and table is actually empty).So, @lowasser what do you think on that? Maybe I should do a deeper dive? Which option do you like the most?
@fdesu ths for the reply very much.maybe i’m more curious in the origin reason why it happens,just let me think about it more