sksamuel / aedile Goto Github PK
View Code? Open in Web Editor NEWKotlin Wrapper for Caffeine
License: Apache License 2.0
Kotlin Wrapper for Caffeine
License: Apache License 2.0
Hello!
When using builder, how com.github.benmanes.caffeine.cache.AsyncCacheLoader#asyncReload
method can be overriden?
For me it should be something like this (not exactly, it's just a stupid example) in com.sksamuel.aedile.core.Builder
class:
fun build(compute: suspend (K) -> V, reloadCompute: suspend (K, V) -> V): LoadingCache<K, V> {
return LoadingCache(scope, caffeine.buildAsync(object : AsyncCacheLoader<K, V> {
override fun asyncLoad(key: K, executor: Executor?): CompletableFuture<out V> {
return scope.async { compute(key) }.asCompletableFuture()
}
override fun asyncReload(key: K, oldValue: V, executor: Executor?): CompletableFuture<out V> {
return scope.async { reloadCompute(key, oldValue) }.asCompletableFuture()
}
}))
}
What do you think?
Am I missing something?
Looked through docs and issues, but haven't found anything about this.
If this functionality is just missing, maybe I can try to prepare a PR...
Or maybe there is some workaround?
I'm wondering whether it would be in scope of this project to maybe do a little bit more than strict wrapping, and add some (opinionated) convenience functions here and there. Specifically, I'm thinking about persisting the cache between application runs, which Caffeine is prepared for, but does not implement directly because "Java serialization is pretty bad" and "The intent is to [...] not [to] dictate how it should be done".
With Kotlin and kotlinx.serialization, serialization is in good shape IMO, and using Protobuf could be a good candidate to save / restore the in-memory cache.
What do you think @sksamuel, would that be a reasonable functionality to add?
It seems aedile-core-1.1.2.jar
is compiled by Java 11 (version 55).
https://s01.oss.sonatype.org/#nexus-search;quick~aedile
Ref: "java.lang.UnsupportedClassVersionError: com/sksamuel/aedile/core/Cache has been compiled by a more recent version of the Java Runtime (class file version 55.0), this version of the Java Runtime only recognizes class file versions up to 52.0"
Could you compile it using Java 8 (version 52) for the sake of compatibility?
Reasons we cannot use Java version higher than 8 include: (1) the kotlin-maven-plugin 1.6.x we use targets java 8, (2) we use maven-jar-plugin to build java 8 jars for other legacy systems to use, and (3) other practical reasons...
(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 [email protected]/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 [email protected]/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 [email protected]/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
at [email protected]/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 [email protected]/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 [email protected]/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 [email protected]/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
at [email protected]/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 :-)
According to the documentation for get
If the specified key is not already associated with a value, attempts to compute its value and enters it into this cache unless null.
However, when I create a cache with non-nullable values and try to update it using a compute
function that can return null I get a compilation error: (scratch file showing the compilation error)
import com.sksamuel.aedile.core.Cache
import com.sksamuel.aedile.core.caffeineBuilder
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
val cache: Cache<String, String> = caffeineBuilder<String, String>().build()
suspend fun foo(): String? {
return cache.get("key") { _ -> asyncFun("a") }
}
suspend fun asyncFun(s: String): String? {
delay(1)
when (s) {
"a" -> return null
else -> return s
}
}
fun main() {
runBlocking {
println("key = ${foo()}")
}
}
main()
Compilation error is on asyncFun:
Type mismatch. Required: String Found: String?
Changing the type of the cache to <String, String?> results in null being saved to the cache (not the desired behavior):
import com.sksamuel.aedile.core.Cache
import com.sksamuel.aedile.core.caffeineBuilder
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
val cache: Cache<String, String?> = caffeineBuilder<String, String?>().build()
suspend fun foo(): String? {
return cache.get("key") { _ -> asyncFun("a") }
}
suspend fun asyncFun(s: String): String? {
delay(1)
when (s) {
"a" -> return null
else -> return s
}
}
fun main() {
runBlocking {
println("key = ${foo()}")
}
}
main()
With caffeine we can implement a loadAll
method which allows us to bulk load the values for multiple keys in a single method call. The aedile Builder build
method only allows us to implement single item loading.
fun build(compute: suspend (K) -> V): LoadingCache<K, V>
While investigating exception handling by Aedile, I noticed that the thread is incorrect when providing a lambda to compute the value if absent.
import com.sksamuel.aedile.core.caffeineBuilder
import org.slf4j.LoggerFactory
suspend fun main() {
val log = LoggerFactory.getLogger("caffeine")
val cache = caffeineBuilder<String, String>().build()
repeat(10) {
log.info(cache.get(it.toString()) { "value" })
}
}
Which results in:
11:28:17.437 [main] INFO caffeine - value
11:28:17.442 [main] INFO caffeine - value
11:28:17.442 [DefaultDispatcher-worker-3] INFO caffeine - value
11:28:17.442 [DefaultDispatcher-worker-3] INFO caffeine - value
11:28:17.444 [DefaultDispatcher-worker-3] INFO caffeine - value
11:28:17.444 [DefaultDispatcher-worker-3] INFO caffeine - value
11:28:17.444 [DefaultDispatcher-worker-3] INFO caffeine - value
11:28:17.444 [DefaultDispatcher-worker-3] INFO caffeine - value
11:28:17.444 [DefaultDispatcher-worker-3] INFO caffeine - value
11:28:17.445 [DefaultDispatcher-worker-3] INFO caffeine - value
Sometimes it shows 3 on the main thread, but in this example 2. I've looked at the sources of Aedile, but I can't figure out exactly what's wrong. The CoroutineScope
is correctly set and used if I'm correct. Any ideas?
@sksamuel first awesome library -- thanks for the effort it took to make this available to the community. I was wondering if there currently exist a way to expose the scheduler option the original library has documented?
After looking at #1, I thought that the exception handling was now done properly by using a SupervisorJob
, however, the following leads to a crashed main thread:
import com.sksamuel.aedile.core.caffeineBuilder
import org.slf4j.LoggerFactory
import kotlin.random.Random
suspend fun main() {
val log = LoggerFactory.getLogger("test")
val cache = caffeineBuilder<String, String>().build()
repeat(10) {
log.info(cache.get(it.toString()) {
if (Random.nextBoolean()) {
throw Exception("test")
}
"value"
})
}
}
Which results in:
11:06:38.853 [main] INFO test - value
Exception in thread "main" java.lang.Exception: test
at dev.trietsch.ApplicationKt$main$2$1.invokeSuspend(Application.kt:58)
at dev.trietsch.ApplicationKt$main$2$1.invoke(Application.kt)
at dev.trietsch.ApplicationKt$main$2$1.invoke(Application.kt)
at com.sksamuel.aedile.core.Cache$get$2$1.invokeSuspend(Cache.kt:43)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:108)
at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:115)
at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:103)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:584)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:793)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:697)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:684)
Aug 23, 2023 11:06:38 AM com.github.benmanes.caffeine.cache.LocalAsyncCache lambda$handleCompletion$7
WARNING: Exception thrown during asynchronous load
java.lang.Exception: test
Am I using the cache wrapper in the wrong way?
I have a LoadingCache
where it's possible for the load function to throw, this causes the get call to return null.
In my scenario I'm caching files so I call get("http://my-url") which may result in a error (e.g. 404).
private val cache = caffeineBuilder<String, ByteArray>()
.buildAll { urls ->
// Download the URLs, may throw an exception or could swallow the exception and leave the result our of the returned map
}
Preload the URLs
cache.getAll(urls)
Use the URLs
val r = cache.get(url)
if(r == null){ // The IDE complains "Condition 'r == null' is always 'false'" but this isn't true
error("Null bytes for URL ${url}.")
}
I think Explicit API mode
should be enabled
Provide an 'Explicit API' mode in the compiler which helps library authoring. Such mode should prevent delivery of unintended public API/ABI to the clients by requiring explicit visibility modifier and explicit return types for public declarations.
As described here this mode will helps to prevent delivery of unintended public API
Hello there, I've been migrating away from caffeine with blocking functions to this lovely library with non-blocking ones, but ran into an issue around ThreadContextElement
s.
We use some libraries that rely on thread locals (boo) but also use Kotlin Coroutines with ThreadContextElement
s (yay) to keep everything working. The issue I've run into is when using an instance of com.sksamuel.aedile.core.Cache
, the ThreadContextElement
is lost and so the compute
function that worked with blocking functions don't work (without the workaround shown below).
Hopefully the following illustrates the situation sufficiently:
package com.example
import com.sksamuel.aedile.core.caffeineBuilder
import kotlinx.coroutines.ThreadContextElement
import kotlinx.coroutines.withContext
import kotlin.coroutines.CoroutineContext
val helloThreadLocal: ThreadLocal<String?> = ThreadLocal.withInitial { null }
class Hello: ThreadContextElement<String?> {
companion object HelloKey : CoroutineContext.Key<Hello>
override val key : CoroutineContext.Key<Hello> = HelloKey
override fun updateThreadContext(context: CoroutineContext): String? {
return helloThreadLocal.get().also {
helloThreadLocal.set("hello")
}
}
override fun restoreThreadContext(context: CoroutineContext, oldState: String?) {
helloThreadLocal.set(oldState)
}
}
suspend fun main() {
val cache = caffeineBuilder<String, String?>().build()
//begin coroutine with thread local context "hello" set
withContext(Hello()) {
//try to retrieve the hello thread local using cache
val result = cache.get("Hello") {
helloThreadLocal.get()
}
//this prints null because context is lost inside compute function :(
println(result)
//however, if we capture the context now and use it inside the cache's compute function...
val callingContext = coroutineContext
val resultUsingCallingContext = cache.get("Hello") {
withContext(callingContext) {
helloThreadLocal.get()
}
}
//hello there!
println(resultUsingCallingContext)
}
}
The result printed is null
, then hello
. As you can see we are able to get around this by capturing the context of the caller and then use it inside the compute
function, which seems to work!
I've noticed scope and dispatcher are configurable, I wanted to ask if it would be possible to either configure the cache to use the caller context (e.g. useCallingContext = true
or something), or do it by default?
As a further note - I don't know if such a suggestion would make sense for compute functions provided when the cache is built (i.e when loading cache is used)? I am specifically asking about when you supply the compute
function at the call site.
Thanks!
Edit: Oh and to clarify, if this is something you'd agree to add into this library I'm more than happy to submit a PR for it!
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.