(first off: Thanks for providing this Kotlin wrapper around Caffeine, highly appreciated!)
I am using the cache with a loader/builder which generates the entries if they don't exist.
The problem is that, if a call to a Loader fails, the cache as a whole becomes unusable afterwards.
I created the following minimal example to demonstrate the issue:
class CacheTest {
@kotlin.test.Test
fun `Simplified test`() {
val list1 = listOf(1, 2)
val list2 = listOf(3, 4)
val sut = CacheTesting()
assertFails { runBlocking { sut.testStuff(list1) } }
val result = runBlocking { sut.testStuff(list2) }
assertTrue { result.size == 2 }
}
}
class CacheTesting {
private val intCache = caffeineBuilder<Int, Int>().build {
calculateValue(it)
}
suspend fun testStuff(ints: List<Int>): List<Int> =
ints.map { intCache.get(it) }
private fun calculateValue(i: Int): Int {
if (i == 2) throw IllegalArgumentException("Bad number")
return i
}
}
The output is:
Parent job is Cancelling
kotlinx.coroutines.JobCancellationException: Parent job is Cancelling; job=JobImpl{Cancelling}@6548fcd1
at app//kotlinx.coroutines.JobSupport.getChildJobCancellationCause(JobSupport.kt:714)
at app//kotlinx.coroutines.JobSupport.createCauseException(JobSupport.kt:720)
at app//kotlinx.coroutines.JobSupport.makeCancelling(JobSupport.kt:752)
at app//kotlinx.coroutines.JobSupport.cancelImpl$kotlinx_coroutines_core(JobSupport.kt:671)
at app//kotlinx.coroutines.JobSupport.parentCancelled(JobSupport.kt:637)
at app//kotlinx.coroutines.ChildHandleNode.invoke(JobSupport.kt:1466)
at app//kotlinx.coroutines.ChildHandleNode.invoke(JobSupport.kt:1462)
at app//kotlinx.coroutines.JobSupport.invokeOnCompletion(JobSupport.kt:1548)
at app//kotlinx.coroutines.Job$DefaultImpls.invokeOnCompletion$default(Job.kt:341)
at app//kotlinx.coroutines.JobSupport.attachChild(JobSupport.kt:970)
at app//kotlinx.coroutines.JobSupport.initParentJob(JobSupport.kt:150)
at app//kotlinx.coroutines.AbstractCoroutine.<init>(AbstractCoroutine.kt:51)
at app//kotlinx.coroutines.DeferredCoroutine.<init>(Builders.common.kt:99)
at app//kotlinx.coroutines.BuildersKt__Builders_commonKt.async(Builders.common.kt:90)
at app//kotlinx.coroutines.BuildersKt.async(Unknown Source)
at app//kotlinx.coroutines.BuildersKt__Builders_commonKt.async$default(Builders.common.kt:82)
at app//kotlinx.coroutines.BuildersKt.async$default(Unknown Source)
at app//com.sksamuel.aedile.core.Builder.build$lambda-0(caffeine.kt:136)
at app//com.github.benmanes.caffeine.cache.LocalAsyncLoadingCache.lambda$newMappingFunction$0(LocalAsyncLoadingCache.java:68)
at app//com.github.benmanes.caffeine.cache.LocalAsyncCache.lambda$get$2(LocalAsyncCache.java:92)
at app//com.github.benmanes.caffeine.cache.UnboundedLocalCache.lambda$computeIfAbsent$2(UnboundedLocalCache.java:298)
at java.base@18.0.1/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1708)
at app//com.github.benmanes.caffeine.cache.UnboundedLocalCache.computeIfAbsent(UnboundedLocalCache.java:294)
at app//com.github.benmanes.caffeine.cache.LocalAsyncCache.get(LocalAsyncCache.java:90)
at app//com.github.benmanes.caffeine.cache.LocalAsyncCache.get(LocalAsyncCache.java:81)
at app//com.github.benmanes.caffeine.cache.LocalAsyncLoadingCache.get(LocalAsyncLoadingCache.java:134)
at app//com.sksamuel.aedile.core.LoadingCache.get(LoadingCache.kt:25)
at app//CacheTesting.testStuff(CacheTest.kt:27)
at app//CacheTest$Simplified test$result$1.invokeSuspend(CacheTest.kt:16)
(Coroutine boundary)
at CacheTesting.testStuff(CacheTest.kt:27)
at CacheTest$Simplified test$result$1.invokeSuspend(CacheTest.kt:16)
Caused by: kotlinx.coroutines.JobCancellationException: Parent job is Cancelling; job=JobImpl{Cancelled}@6548fcd1
at app//kotlinx.coroutines.JobSupport.getChildJobCancellationCause(JobSupport.kt:714)
at app//kotlinx.coroutines.JobSupport.createCauseException(JobSupport.kt:720)
at app//kotlinx.coroutines.JobSupport.makeCancelling(JobSupport.kt:752)
at app//kotlinx.coroutines.JobSupport.cancelImpl$kotlinx_coroutines_core(JobSupport.kt:671)
at app//kotlinx.coroutines.JobSupport.parentCancelled(JobSupport.kt:637)
at app//kotlinx.coroutines.ChildHandleNode.invoke(JobSupport.kt:1466)
at app//kotlinx.coroutines.ChildHandleNode.invoke(JobSupport.kt:1462)
at app//kotlinx.coroutines.JobSupport.invokeOnCompletion(JobSupport.kt:1548)
at app//kotlinx.coroutines.Job$DefaultImpls.invokeOnCompletion$default(Job.kt:341)
at app//kotlinx.coroutines.JobSupport.attachChild(JobSupport.kt:970)
at app//kotlinx.coroutines.JobSupport.initParentJob(JobSupport.kt:150)
at app//kotlinx.coroutines.AbstractCoroutine.<init>(AbstractCoroutine.kt:51)
at app//kotlinx.coroutines.DeferredCoroutine.<init>(Builders.common.kt:99)
at app//kotlinx.coroutines.BuildersKt__Builders_commonKt.async(Builders.common.kt:90)
at app//kotlinx.coroutines.BuildersKt.async(Unknown Source)
at app//kotlinx.coroutines.BuildersKt__Builders_commonKt.async$default(Builders.common.kt:82)
at app//kotlinx.coroutines.BuildersKt.async$default(Unknown Source)
at app//com.sksamuel.aedile.core.Builder.build$lambda-0(caffeine.kt:136)
at app//com.github.benmanes.caffeine.cache.LocalAsyncLoadingCache.lambda$newMappingFunction$0(LocalAsyncLoadingCache.java:68)
at app//com.github.benmanes.caffeine.cache.LocalAsyncCache.lambda$get$2(LocalAsyncCache.java:92)
at app//com.github.benmanes.caffeine.cache.UnboundedLocalCache.lambda$computeIfAbsent$2(UnboundedLocalCache.java:298)
at java.base@18.0.1/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1708)
at app//com.github.benmanes.caffeine.cache.UnboundedLocalCache.computeIfAbsent(UnboundedLocalCache.java:294)
at app//com.github.benmanes.caffeine.cache.LocalAsyncCache.get(LocalAsyncCache.java:90)
at app//com.github.benmanes.caffeine.cache.LocalAsyncCache.get(LocalAsyncCache.java:81)
at app//com.github.benmanes.caffeine.cache.LocalAsyncLoadingCache.get(LocalAsyncLoadingCache.java:134)
at app//com.sksamuel.aedile.core.LoadingCache.get(LoadingCache.kt:25)
at app//CacheTesting.testStuff(CacheTest.kt:27)
at app//CacheTest$Simplified test$result$1.invokeSuspend(CacheTest.kt:16)
at app//kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at app//kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at app//kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:284)
at app//kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:85)
at app//kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59)
at app//kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
at app//kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:38)
at app//kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
at app//CacheTest.Simplified test(CacheTest.kt:16)
at java.base@18.0.1/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
at java.base@18.0.1/java.lang.reflect.Method.invoke(Method.java:577)
at app//org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:727)
at app//org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
at app//org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
at app//org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:156)
at app//org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:147)
at app//org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:86)
at app//org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(InterceptingExecutableInvoker.java:103)
at app//org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.lambda$invoke$0(InterceptingExecutableInvoker.java:93)
at app//org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
at app//org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
at app//org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
at app//org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
at app//org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:92)
at app//org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:86)
at app//org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:217)
at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at app//org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:213)
at app//org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:138)
at app//org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:68)
at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151)
at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
at app//org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
at java.base@18.0.1/java.util.ArrayList.forEach(ArrayList.java:1511)
at app//org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
at app//org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
at java.base@18.0.1/java.util.ArrayList.forEach(ArrayList.java:1511)
at app//org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
at app//org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
at app//org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35)
at app//org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
at app//org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:107)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86)
at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86)
at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:53)
at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:99)
at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$000(JUnitPlatformTestClassProcessor.java:79)
at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:75)
at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:61)
at java.base@18.0.1/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
at java.base@18.0.1/java.lang.reflect.Method.invoke(Method.java:577)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
at jdk.proxy1/jdk.proxy1.$Proxy2.stop(Unknown Source)
at org.gradle.api.internal.tasks.testing.worker.TestWorker$3.run(TestWorker.java:193)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:129)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:100)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:60)
at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:133)
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:71)
at app//worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
at app//worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
Caused by: java.lang.IllegalArgumentException: Bad number
at CacheTesting.calculateValue(CacheTest.kt:30)
...
I expect this test to pass as, in my understanding, the second call to sut.testStuff() should not depend on the outcome of the first one.
I am not sure if this really is a bug or just a misunderstanding on my part, but I know for sure that this currently leads to a broken system :-)
(first off: Thanks for providing this Kotlin wrapper around Caffeine, highly appreciated!)
I am using the cache with a loader/builder which generates the entries if they don't exist.
The problem is that, if a call to a Loader fails, the cache as a whole becomes unusable afterwards.
I created the following minimal example to demonstrate the issue:
The output is:
I expect this test to pass as, in my understanding, the second call to
sut.testStuff()
should not depend on the outcome of the first one.I am not sure if this really is a bug or just a misunderstanding on my part, but I know for sure that this currently leads to a broken system :-)