diff --git a/guava-tests/test/com/google/common/cache/LocalCacheTest.java b/guava-tests/test/com/google/common/cache/LocalCacheTest.java index 15ceacaae4bf..47258e33e8ef 100644 --- a/guava-tests/test/com/google/common/cache/LocalCacheTest.java +++ b/guava-tests/test/com/google/common/cache/LocalCacheTest.java @@ -1077,6 +1077,44 @@ public void testRemovalListener_collected() { assertThat(listener.isEmpty()).isTrue(); } + // Test for https://github.com/google/guava/issues/7985 + // When compute() returns null for a collected entry, the removal cause should be COLLECTED. + public void testComputeRemovalCause_collected() { + QueuingRemovalListener listener = queuingRemovalListener(); + LocalCache map = + makeLocalCache( + createCacheBuilder().concurrencyLevel(1).softValues().removalListener(listener)); + Segment segment = map.segments[0]; + assertThat(listener.isEmpty()).isTrue(); + + Object key = new Object(); + Object value = new Object(); + + // Put an entry into the cache + map.put(key, value); + assertThat(listener.isEmpty()).isTrue(); + + // Get the entry and clear its value reference to simulate garbage collection + int hash = map.hash(key); + ReferenceEntry entry = segment.getEntry(key, hash); + ValueReference valueReference = entry.getValueReference(); + + // Simulate garbage collection by clearing the value reference + ((java.lang.ref.Reference) valueReference).clear(); + + // Now call compute() with a function that returns null + // Since the value was collected, the removal cause should be COLLECTED, not EXPLICIT + Object unused = map.compute(key, (k, v) -> { + // The value should be null since it was collected + assertThat(v).isNull(); + return null; + }); + + // Verify the removal notification has COLLECTED cause + assertNotified(listener, key, null, RemovalCause.COLLECTED); + assertThat(listener.isEmpty()).isTrue(); + } + public void testRemovalListener_expired() { FakeTicker ticker = new FakeTicker(); QueuingRemovalListener listener = queuingRemovalListener(); diff --git a/guava/src/com/google/common/cache/LocalCache.java b/guava/src/com/google/common/cache/LocalCache.java index 3fa8b8a599e6..bce4c9ba71c2 100644 --- a/guava/src/com/google/common/cache/LocalCache.java +++ b/guava/src/com/google/common/cache/LocalCache.java @@ -2287,7 +2287,13 @@ V waitForLoadingValue(ReferenceEntry e, K key, ValueReference valueR removeLoadingValue(key, hash, computingValueReference); return null; } else { - removeEntry(e, hash, RemovalCause.EXPLICIT); + // If the value was garbage collected, report COLLECTED; otherwise EXPLICIT. + V oldValue = valueReference.get(); + RemovalCause cause = + (oldValue == null && valueReference.isActive()) + ? RemovalCause.COLLECTED + : RemovalCause.EXPLICIT; + removeEntry(e, hash, cause); return null; } } finally {