svarog
svarog

Reputation: 9847

Caffeine: Can't provide CacheWriter to AsyncLoadingCache

I'm trying to write a AsyncLoadingCache that accepts a CacheWriter and I'm getting an IllegalStateException.

Here's my code:

CacheWriter<String, UUID> cacheWriter = new CacheWriter<String, UUID>() {
        @Override
        public void write(String key, UUID value) {

        }

        @Override
        public void delete(String key, UUID value, RemovalCause cause) {

        }
    };

AsyncLoadingCache<String, UUID> asyncCache = Caffeine.newBuilder()
        .expireAfterWrite(60, TimeUnit.SECONDS)
        .writer(cacheWriter)
        .maximumSize(100L)
        .buildAsync((String s) -> { /* <== line 41, exception occurs here */
            return UUID.randomUUID();
        });

And I'm getting this trace

Exception in thread "main" java.lang.IllegalStateException at com.github.benmanes.caffeine.cache.Caffeine.requireState(Caffeine.java:174) at com.github.benmanes.caffeine.cache.Caffeine.buildAsync(Caffeine.java:854) at com.mycompany.caffeinetest.Main.main(Main.java:41)

If I'll change the cache to a LoadingCache or remove .writer(cacheWriter) the code will run properly. What am I doing wrong? it seems I'm providing the right types to both objects.

Upvotes: 1

Views: 1292

Answers (1)

Ben Manes
Ben Manes

Reputation: 9621

Unfortunately these two features are incompatible. While the documentation states this, I have updated the exception to communicate this better. In Caffeine.writer it states,

This feature cannot be used in conjunction with {@link #weakKeys()} or {@link #buildAsync}.

A CacheWriter is a synchronous interceptor for a mutation of an entry. For example, it might be used to evict into a disk cache as a secondary layer, whereas a RemovalListener is asynchronous and using it would leave a race where the entry is not present in either caches. The mechanism is to use ConcurrentHashMap's compute methods to perform the write or removal, and call into the CacheWriter within that block.

In AsyncLoadingCache, the value materializes later when the CompletableFuture is successful, or is automatically removed if null or an error. When the entry is modified within the hash table, this future may be in-flight. This would mean that the CacheWriter would often be called without the materialized value and likely cannot do very intelligent things.

From an API perspective, unfortunately telescoping builders (which use the type system to disallow incompatible chains) become more confusing than using runtime exceptions. Sorry for not making the error clear, which should now be fixed.

Upvotes: 3

Related Questions