-
Notifications
You must be signed in to change notification settings - Fork 11k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
LocalManualCache deadlock in guava 21 #2743
Comments
Reproduced. This is due to mixing synchronization primitives and non-deterministic ordering for lock acquisition. lock {
// note valueReference can be an existing value or even itself another loading value if
// the value for the key is already being computed.
loadingValueReference = new LoadingValueReference<K, V>(valueReference);
if (e == null) {
// create entry...
}
e.setValueReference(loadingValueReference);
}
...
synchronized (e) {
V newValue = loadingValueReference.compute(key, function);
...
} This creates a computation chain where each computer waits for the preceding one to complete. The order of acquiring the intrinsic lock on The assumption was probably that, like (Verified Caffeine passes this test case) |
I tried running this in my ComputeBenchmark and it deadlocks immediately. This is despite being fully populated, because of the execution chain mentioned above. The execution chain means the locking is pessimistic, which is also true of Another area of concern is that the internal |
#2743 we need to hold the lock when calling compute, the rest of the calls like removeEntry need to hold the lock too. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=150363160
Are there plans to backport this fix to 21.0 and release a patched version? |
Here are tests I wrote to characterize the behavior. With 2 threads, neither thread deadlocks. Every single additional thread deadlocks. @Test
public void noDeadlocksWithOnlyTwoThreads() throws Exception {
attemptToDeadlock(2);
}
@Test(expected = TimeoutException.class)
public void allAdditionalThreadsDeadlock() throws Exception {
attemptToDeadlock(3);
}
private void attemptToDeadlock(final int nThreads) throws Exception {
final Cache<String, String> cache = CacheBuilder.newBuilder().build();
final Supplier<String> loadThroughCache = () ->
cache.asMap().computeIfAbsent("foo", key -> {
// simulate expensive operation
Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);
return "bar";
});
final ExecutorService threadPool = Executors.newFixedThreadPool(nThreads);
CompletableFuture.allOf(Stream.generate(() ->
CompletableFuture.supplyAsync(loadThroughCache, threadPool))
.limit(nThreads)
.toArray(CompletableFuture[]::new))
.get(1, TimeUnit.SECONDS);
} |
I faced with this issue today, it was quite unexpected. |
Until this is released, you could use Caffeine as a stopgap. |
When trying to upgrade my test suit, I found that the new computation support will deadlock on recursive invocations. For a load() operation this is detected to fail fast. Note that JDK8's An example test that fails with Guava but passes with Caffeine (backed by JDK8's CHM) is, @Test(dataProvider = "caches", expectedExceptions = StackOverflowError.class)
public void computeIfPresent_recursive(Map<Integer, Integer> map) {
// As we cannot provide immediate checking without an expensive solution, e.g. ThreadLocal,
// instead we assert that a stack overflow error will occur to inform the developer (vs
// a live-lock or deadlock alternative).
Integer key = 100;
BiFunction<Integer, Integer, Integer> mappingFunction =
new BiFunction<Integer, Integer, Integer>() {
boolean recursed;
@Override public Integer apply(Integer key, Integer value) {
if (recursed) {
throw new StackOverflowError();
}
recursed = true;
return map.computeIfPresent(key, this);
}
};
map.put(key, key);
map.computeIfPresent(key, mappingFunction);
} Secondly, the resulting statistics differ between Caffeine and Guava for computations. I haven't narrowed down to a compatible adapter, but some of the differences appear to be...
Caffeine's tests against its cache and Guava's to verify compatibility. The adapter currently does not use the compute methods, but ideally with v22 it would migrate over. Due to the stats that is hard to flush out (the deadlocking tests can be disabled using an implementation flag). |
Here are the differences required for test compatibility. |
Multithreaded tests fail when computations are intermixed with loads. A |
The dead lock issue is fixed: [1]. [1] google/guava#2743 Bug: Issue 7645 Change-Id: I77dd930503e6869be207ca4a4f2fd85116719506
Unit test testCache works with guava 20, locks up in 21 (LocalCache.java,line 2392):
The text was updated successfully, but these errors were encountered: