-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Description
Thank you for the great library!
I have a setup where I use a cache to store the result of a network request that can possibly be very slow. Certain user interactions are blocking on having the value, so the goal is to avoid cache misses at all costs. To achieve this, I use a removal listener that checks whether the removed entry was evicted or explicitly removed, and if so calls refresh to reload the entry. The actual size of the cache is very small, so there are no memory concerns with preventing entries from being removed.
I'm having an issue after a recent bump from 3.1.2 to 3.1.3, which I believe to have been introduced by this change: 68fbff8. After that change, the removeNode call causes values to be evicted and refreshed, followed by the subsequent remove call which removes and refreshes values a second time.
Here's an example test that works on version 3.1.2 but not on 3.1.3:
import static org.assertj.core.api.Assertions.assertThat;
import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.github.benmanes.caffeine.cache.RemovalCause;
import com.google.common.util.concurrent.MoreExecutors;
import java.util.concurrent.TimeUnit;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.junit.jupiter.api.Test;
public class CacheTest {
private final LoadingCache<Integer, Integer> cache = Caffeine.newBuilder()
.expireAfterWrite(5, TimeUnit.MINUTES)
.removalListener(this::listenRemoval)
.executor(MoreExecutors.directExecutor())
.recordStats()
.build(new CacheLoader<>() {
@Override
public @Nullable Integer load(Integer _key) {
return 1;
}
});
private void listenRemoval(Integer key, Integer _value, RemovalCause cause) {
// We don't want to reload if the value was just replaced
if (cause.wasEvicted() || cause == RemovalCause.EXPLICIT) {
cache.refresh(key);
}
}
@Test
public void testCacheEvictionRefresh() {
cache.get(1);
cache.invalidateAll();
assertThat(cache.stats().loadCount()).isEqualTo(2);
}
}
Note that this is only an issue when using a direct executor or very fast async refresh, where the value is refreshed and re-added to the cache between removeNode and remove.