-
|
The fact that reentrant computations are not allowed in caffeine is kind of blocker for me, can't be avoided, so I needed to downgrade to guava cache that allows them. I had an idea to work around this limitation at the expense of not caching the reentrant computations. That is, instead of failing, the reentring computation is simply evaluated without being cached. Only when the computation is evaluated again as top level, can be cached. It seems to me as better approach than just failing. What do you think? The implementation is to wrap the cache into a wrapper (I use wrapper anyway, to decouple caching from actual implementation) class MyCache<K, V>(
private val cache: Cache<K, V>
) {
private val inLoader = ThreadLocal()
fun <K: Any, V: Any> get(key: K, compute: () -> V): V =
if (inLoader.get() == true) {
// return cached value if present
cache.getIfPresent(key)?.let { return it }
// recursion detected, compute without caching
compute()
} else {
// top-level load
inLoader.set(true)
try {
cache.get(key) { compute() }
} finally {
inLoader.remove()
}
}
} |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 5 replies
-
|
The limitation comes from ConcurrentHashMap. Guava handles it better because it used to deadlock until I added detection using Have you tried the future-style approach in the FAQ? That would let you cache the results while also recursing outside of any map locks. |
Beta Was this translation helpful? Give feedback.
Ah, looks like I misunderstood the proposed solution in the FAQ. So the trick is not to offload the computation, but only to postpone it and evaluate it outside the cache lock. The generalized solution (universal wrapper) could then be
The tradeoff here is that the CompletableFuture is always instantiated, providing some overhead. So I might as well combine the two approaches, and use reentrancy detection, and create the future only when neede…