Search code examples
javaguava

Invalidating an entry while iterating over guava cache entries


I'm using a guava cache and I have a use case where I need to iterate over the cache entries and invalidate them if they meet a certain requirement. The code looks something like below:

Cache<String, String> cache = CacheBuilder.newBuilder()
                                          .expireAfterWrite(30, TimeUnit.MINUTES)
                                          .build();

for (String s : cache.asMap().keySet()) {
    if (someCalculation(s)) {
        cache.invalidate(s);
    }
}

I didn't see any inconsistency in cache entries after running the above piece of code. I'm asking this question to see if this always gives me consistent results and if there is a better way of solving my use case.


Solution

  • I don't think your way is correct. The asMap documentation says:

    Iterators from the returned map are at least weakly consistent: they are safe for concurrent use, but if the cache is modified (including by eviction) after the iterator is created, it is undefined which of the changes (if any) will be reflected in that iterator.

    This is relevant to what you are doing, as you are iterating while invalidating, so it's undefined what happens to your iterator (which is implicit in the for loop) after cache invalidation.

    I would use the fact that (again from the doc):

    Modifications made to the map directly affect the cache.

    And a neat way to modify a map is to modify its keyset (the Java API doc says "The set is backed by the map, so changes to the map are reflected in the set, and vice-versa.")

    This is how I would do it:

    Cache<String, String> cache = CacheBuilder.newBuilder()
            .expireAfterWrite(30, TimeUnit.MINUTES)
            .build();
    
    cache.asMap().keySet().removeIf(s -> someCalculation(s));