Skip to content

Commit 42bf4f4

Browse files
ben-manesGoogle Java Core Libraries
authored andcommitted
Fix compatibility between the cache compute methods and a load.
Fixes #5348 Fixes #5342 Fixes #2827 Resolves underlying cause of #2108 RELNOTES=Fix compatibility between the cache compute methods and a load. PiperOrigin-RevId: 357192480
1 parent 7c566b5 commit 42bf4f4

File tree

2 files changed

+87
-10
lines changed

2 files changed

+87
-10
lines changed

guava-tests/test/com/google/common/cache/LocalCacheMapComputeTest.java

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@
1818

1919
import static com.google.common.truth.Truth.assertThat;
2020

21+
import com.google.common.util.concurrent.UncheckedExecutionException;
22+
import java.util.ArrayList;
23+
import java.util.List;
24+
import java.util.Queue;
25+
import java.util.concurrent.ConcurrentLinkedQueue;
26+
import java.util.concurrent.ExecutionException;
2127
import java.util.concurrent.TimeUnit;
2228
import java.util.function.IntConsumer;
2329
import java.util.stream.IntStream;
@@ -91,6 +97,31 @@ public void testComputeIfPresent() {
9197
assertThat(cache.getIfPresent(key).split(delimiter)).hasLength(count + 1);
9298
}
9399

100+
public void testComputeIfPresentRemove() {
101+
List<RemovalNotification<Integer, Integer>> notifications = new ArrayList<>();
102+
Cache<Integer, Integer> cache =
103+
CacheBuilder.newBuilder()
104+
.removalListener(
105+
new RemovalListener<Integer, Integer>() {
106+
@Override
107+
public void onRemoval(RemovalNotification<Integer, Integer> notification) {
108+
notifications.add(notification);
109+
}
110+
})
111+
.build();
112+
cache.put(1, 2);
113+
114+
// explicitly remove the existing value
115+
cache.asMap().computeIfPresent(1, (key, value) -> null);
116+
assertThat(notifications).hasSize(1);
117+
CacheTesting.checkEmpty(cache);
118+
119+
// ensure no zombie entry remains
120+
cache.asMap().computeIfPresent(1, (key, value) -> null);
121+
assertThat(notifications).hasSize(1);
122+
CacheTesting.checkEmpty(cache);
123+
}
124+
94125
public void testUpdates() {
95126
cache.put(key, "1");
96127
// simultaneous update for same key, some null, some non-null
@@ -113,6 +144,41 @@ public void testCompute() {
113144
assertEquals(0, cache.size());
114145
}
115146

147+
public void testComputeWithLoad() {
148+
Queue<RemovalNotification<String, String>> notifications = new ConcurrentLinkedQueue<>();
149+
cache =
150+
CacheBuilder.newBuilder()
151+
.removalListener(
152+
new RemovalListener<String, String>() {
153+
@Override
154+
public void onRemoval(RemovalNotification<String, String> notification) {
155+
notifications.add(notification);
156+
}
157+
})
158+
.expireAfterAccess(500000, TimeUnit.MILLISECONDS)
159+
.maximumSize(count)
160+
.build();
161+
162+
cache.put(key, "1");
163+
// simultaneous load and deletion
164+
doParallelCacheOp(
165+
count,
166+
n -> {
167+
try {
168+
cache.get(key, () -> key);
169+
cache.asMap().compute(key, (k, v) -> null);
170+
} catch (ExecutionException e) {
171+
throw new UncheckedExecutionException(e);
172+
}
173+
});
174+
175+
CacheTesting.checkEmpty(cache);
176+
for (RemovalNotification<String, String> entry : notifications) {
177+
assertThat(entry.getKey()).isNotNull();
178+
assertThat(entry.getValue()).isNotNull();
179+
}
180+
}
181+
116182
public void testComputeExceptionally() {
117183
try {
118184
doParallelCacheOp(

guava/src/com/google/common/cache/LocalCache.java

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2188,7 +2188,7 @@ V waitForLoadingValue(ReferenceEntry<K, V> e, K key, ValueReference<K, V> valueR
21882188
V compute(K key, int hash, BiFunction<? super K, ? super V, ? extends V> function) {
21892189
ReferenceEntry<K, V> e;
21902190
ValueReference<K, V> valueReference = null;
2191-
LoadingValueReference<K, V> loadingValueReference = null;
2191+
ComputingValueReference<K, V> computingValueReference = null;
21922192
boolean createNewEntry = true;
21932193
V newValue;
21942194

@@ -2229,33 +2229,33 @@ V compute(K key, int hash, BiFunction<? super K, ? super V, ? extends V> functio
22292229

22302230
// note valueReference can be an existing value or even itself another loading value if
22312231
// the value for the key is already being computed.
2232-
loadingValueReference = new LoadingValueReference<>(valueReference);
2232+
computingValueReference = new ComputingValueReference<>(valueReference);
22332233

22342234
if (e == null) {
22352235
createNewEntry = true;
22362236
e = newEntry(key, hash, first);
2237-
e.setValueReference(loadingValueReference);
2237+
e.setValueReference(computingValueReference);
22382238
table.set(index, e);
22392239
} else {
2240-
e.setValueReference(loadingValueReference);
2240+
e.setValueReference(computingValueReference);
22412241
}
22422242

2243-
newValue = loadingValueReference.compute(key, function);
2243+
newValue = computingValueReference.compute(key, function);
22442244
if (newValue != null) {
22452245
if (valueReference != null && newValue == valueReference.get()) {
2246-
loadingValueReference.set(newValue);
2246+
computingValueReference.set(newValue);
22472247
e.setValueReference(valueReference);
22482248
recordWrite(e, 0, now); // no change in weight
22492249
return newValue;
22502250
}
22512251
try {
22522252
return getAndRecordStats(
2253-
key, hash, loadingValueReference, Futures.immediateFuture(newValue));
2253+
key, hash, computingValueReference, Futures.immediateFuture(newValue));
22542254
} catch (ExecutionException exception) {
22552255
throw new AssertionError("impossible; Futures.immediateFuture can't throw");
22562256
}
2257-
} else if (createNewEntry) {
2258-
removeLoadingValue(key, hash, loadingValueReference);
2257+
} else if (createNewEntry || valueReference.isLoading()) {
2258+
removeLoadingValue(key, hash, computingValueReference);
22592259
return null;
22602260
} else {
22612261
removeEntry(e, hash, RemovalCause.EXPLICIT);
@@ -3603,6 +3603,17 @@ public ValueReference<K, V> copyFor(
36033603
}
36043604
}
36053605

3606+
static class ComputingValueReference<K, V> extends LoadingValueReference<K, V> {
3607+
ComputingValueReference(ValueReference<K, V> oldValue) {
3608+
super(oldValue);
3609+
}
3610+
3611+
@Override
3612+
public boolean isLoading() {
3613+
return false;
3614+
}
3615+
}
3616+
36063617
// Queues
36073618

36083619
/**
@@ -3927,7 +3938,7 @@ long longSize() {
39273938
Segment<K, V>[] segments = this.segments;
39283939
long sum = 0;
39293940
for (int i = 0; i < segments.length; ++i) {
3930-
sum += Math.max(0, segments[i].count); // see https://github.com/google/guava/issues/2108
3941+
sum += segments[i].count;
39313942
}
39323943
return sum;
39333944
}

0 commit comments

Comments
 (0)