Coder Social home page Coder Social logo

aedile's People

Contributors

bmaggi avatar conhan93 avatar sksamuel avatar stigkj avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

aedile's Issues

How to override com.github.benmanes.caffeine.cache.AsyncCacheLoader#asyncReload method

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?

Ideas for trivial convenience features that go beyond strict wrapping

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?

Could you compile the jar using Java 8?

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...

Exception in CacheLoader leads to broken cache

(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 :-)

get(key: K, compute: suspend (K) -> V ) doesn't support `compute` function that returns null

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()

Support bulk mapping functions

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>

Dispatcher thread is incorrect

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?

Exceptions crash the caller thread

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?

`get` method is typed as non-null but if the load function fails it will return null

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}.")
}

Enforce explicit API visibility modifiers

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

ThreadContextElements are lost in compute function of Caches

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 ThreadContextElements.

We use some libraries that rely on thread locals (boo) but also use Kotlin Coroutines with ThreadContextElements (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!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.