quarkusio / quarkus

Quarkus: Supersonic Subatomic Java.
https://quarkus.io
Apache License 2.0
13.72k stars 2.67k forks source link

Quarkus cache does not work with kotlin suspend functions #23746

Open u6f6o opened 2 years ago

u6f6o commented 2 years ago

Describe the bug

Quarkus-cache does not work correctly with kotlin suspend functions. The following code snippet

@ApplicationScoped
class StationAmbassador(
    @RestClient
    private val stationConfigClient: StationConfigClient
) {

    suspend fun stationConfig(stationId: String): StationConfig? {
        return stationConfig()[stationId]
    }

    @CacheResult(cacheName = "station-config")
    suspend fun stationConfig(): Map<String, StationConfig> {
        val snapshot = stationConfigClient.loadStationConfig()

        Log.infof(
            "Fetched {} stations (hd: {}, sd: {}, newTv: {}) from product-configuration.",
            snapshot.stationsCount,
            snapshot.hdStationsCount,
            snapshot.sdStationsCount,
            snapshot.newTvStationsCount
        )
        return snapshot.stations.associateBy { it.id }
    }
}

results in

2022-02-15 19:50:11,168 ERROR [io.qua.ver.htt.run.QuarkusErrorHandler] (vert.x-eventloop-thread-3) HTTP Request to /api/station-config failed, error id: 2e427033-ad2e-4129-9391-a8fd872be97f-1: java.lang.IllegalStateException: The current thread cannot be blocked: vert.x-eventloop-thread-3
    at io.smallrye.mutiny.operators.uni.UniBlockingAwait.await(UniBlockingAwait.java:30)
    at io.smallrye.mutiny.groups.UniAwait.atMost(UniAwait.java:65)
    at io.smallrye.mutiny.groups.UniAwait.indefinitely(UniAwait.java:46)
    at io.quarkus.cache.runtime.CacheResultInterceptor.intercept(CacheResultInterceptor.java:115)
    at io.quarkus.cache.runtime.CacheResultInterceptor_Bean.intercept(Unknown Source)
    at io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:41)
    at io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:41)
    at io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:32)
    at stationconfig.StationAmbassador_Subclass.stationConfig(Unknown Source)
    at stationconfig.StationAmbassador.stationConfig$suspendImpl(StationAmbassador.kt:17)
    at stationconfig.StationAmbassador.stationConfig(StationAmbassador.kt)
    at stationconfig.StationAmbassador_Subclass.stationConfig$$superforward1(Unknown Source)
    at stationconfig.StationAmbassador_Subclass$$function$$1.apply(Unknown Source)

We can workaround this behaviour by returning a CompletionStage from the rest client and work with unis instead.

Expected behavior

Quarkus-cache should work with suspend functions as well.

Actual behavior

2022-02-15 19:50:11,168 ERROR [io.qua.ver.htt.run.QuarkusErrorHandler] (vert.x-eventloop-thread-3) HTTP Request to /api/station-config failed, error id: 2e427033-ad2e-4129-9391-a8fd872be97f-1: java.lang.IllegalStateException: The current thread cannot be blocked: vert.x-eventloop-thread-3
    at io.smallrye.mutiny.operators.uni.UniBlockingAwait.await(UniBlockingAwait.java:30)
    at io.smallrye.mutiny.groups.UniAwait.atMost(UniAwait.java:65)
    at io.smallrye.mutiny.groups.UniAwait.indefinitely(UniAwait.java:46)
    at io.quarkus.cache.runtime.CacheResultInterceptor.intercept(CacheResultInterceptor.java:115)
    at io.quarkus.cache.runtime.CacheResultInterceptor_Bean.intercept(Unknown Source)
    at io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:41)
    at io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:41)
    at io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:32)
    at stationconfig.StationAmbassador_Subclass.stationConfig(Unknown Source)
    at stationconfig.StationAmbassador.stationConfig$suspendImpl(StationAmbassador.kt:17)
    at stationconfig.StationAmbassador.stationConfig(StationAmbassador.kt)
    at stationconfig.StationAmbassador_Subclass.stationConfig$$superforward1(Unknown Source)
    at stationconfig.StationAmbassador_Subclass$$function$$1.apply(Unknown Source)

How to Reproduce?

No response

Output of uname -a or ver

Darwin MB-07-P15.fritz.box 19.6.0 Darwin Kernel Version 19.6.0: Mon Aug 31 22:12:52 PDT 2020; root:xnu-6153.141.2~1/RELEASE_X86_64 x86_64

Output of java -version

openjdk version "11.0.13" 2021-10-19 OpenJDK Runtime Environment GraalVM CE 20.3.4 (build 11.0.13+7-jvmci-20.3-b24) OpenJDK 64-Bit Server VM GraalVM CE 20.3.4 (build 11.0.13+7-jvmci-20.3-b24, mixed mode, sharing)

GraalVM version (if different from Java)

No response

Quarkus version or git rev

2.7.0.Final

Build tool (ie. output of mvnw --version or gradlew --version)

Gradle 7.3.3

Additional information

No response

quarkus-bot[bot] commented 2 years ago

/cc @evanchooly, @gwenneg

u6f6o commented 2 years ago

A possible workaround for this issue is to wrap the cached method and to use the kotlin-mutiny lib:

@ApplicationScoped
class StationAmbassador(
    @RestClient
    private val stationConfigClient: StationConfigClient
) {

    suspend fun stationConfig(stationId: String): StationConfig? {
        return stationConfig().awaitSuspending()[stationId]
    }

    @CacheResult(cacheName = "station-config")
    fun stationConfig(): Uni<Map<String, StationConfig>> {
        return stationConfigClient
            .loadStationConfig()
            .onItem()
            .transform { snapshot ->
                Log.infof(
                    "Fetched {} stations (hd: {}, sd: {}, newTv: {}) from product-configuration.",
                    snapshot.stationsCount,
                    snapshot.hdStationsCount,
                    snapshot.sdStationsCount,
                    snapshot.newTvStationsCount
                )
                snapshot.stations.associateBy { it.id }
            }
    }
}
evanchooly commented 2 years ago

I'm not familiar enough with all this to create a reproducer on my own. Can you put one together and I'll dig in to it? Thanks.

u6f6o commented 2 years ago

Sure 👍 : https://github.com/u6f6o/quarkus-suspend-cache-issue If you run the test, it should fail with someting like:

Mar 14, 2022 6:27:56 PM io.quarkus.vertx.http.runtime.QuarkusErrorHandler handle
ERROR: HTTP Request to /hello failed, error id: 07c07c3e-4ed8-4a00-88e0-f90c9c69f60d-1
java.lang.IllegalStateException: The current thread cannot be blocked: vert.x-eventloop-thread-9 @coroutine#1
    at io.smallrye.mutiny.operators.uni.UniBlockingAwait.await(UniBlockingAwait.java:30)
    at io.smallrye.mutiny.groups.UniAwait.atMost(UniAwait.java:65)
    at io.smallrye.mutiny.groups.UniAwait.indefinitely(UniAwait.java:46)
    at io.quarkus.cache.runtime.CacheResultInterceptor.intercept(CacheResultInterceptor.java:115)
    at io.quarkus.cache.runtime.CacheResultInterceptor_Bean.intercept(Unknown Source)
    at io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:41)
    at io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:41)
    at io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:32)
    at org.acme.GreetingService_Subclass.sayMyName(Unknown Source)
    at org.acme.GreetingService_ClientProxy.sayMyName(Unknown Source)
    at org.acme.GreetingResource.hello$suspendImpl(GreetingResource.kt:16)
    at org.acme.GreetingResource.hello(GreetingResource.kt)
    at org.acme.GreetingResource$quarkuscoroutineinvoker$hello_efb111fc5da56e7b2d25b0ef5b3b839c46bb2656.invokeCoroutine(Unknown Source)
    at org.jboss.resteasy.reactive.server.runtime.kotlin.CoroutineInvocationHandler$handle$1.invokeSuspend(CoroutineInvocationHandler.kt:40)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
    at org.jboss.resteasy.reactive.server.runtime.kotlin.VertxDispatcher.dispatch$lambda-0(ApplicationCoroutineScope.kt:39)
    at io.vertx.core.impl.AbstractContext.dispatch(AbstractContext.java:100)
    at io.vertx.core.impl.AbstractContext.dispatch(AbstractContext.java:63)
    at io.vertx.core.impl.EventLoopContext.lambda$runOnContext$0(EventLoopContext.java:38)
    at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:164)
    at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:469)
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:500)
    at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:986)
    at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
    at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
    at java.base/java.lang.Thread.run(Thread.java:829)
evanchooly commented 2 years ago

So I've poked at this a bit and i think the changes (more specifically where) that are likely needed needs someone a bit more familiar with the caching code. Perhaps @mkouba would know or know who to ping?

gwenneg commented 2 years ago

I can help with that @evanchooly.

geoand commented 2 years ago

Is someone working on this? If not, I can pick it up

gwenneg commented 2 years ago

Sorry, I'm swamped with other subjects, I couldn't find the time to look at this one so far. Feel free to pick it up @geoand.

geoand commented 2 years ago

@evanchooly have you started looking into this?

gwenneg commented 2 years ago

This issue and #21592 could be related.

evanchooly commented 2 years ago

I didn't get far. I'm out this week at a conference so please take it.

geoand commented 2 years ago

I doubt I'll have time this week, but if I do, I'll let you know

stephan-strate commented 2 years ago

Any updates on this issue?

geoand commented 2 years ago

Nope, things have pilled up so this has moved to the back of my list

mschorsch commented 2 years ago

@geoand Any updates? Maybe for 2.13 😄 ?

geoand commented 2 years ago

Not a chance :)

geoand commented 2 years ago

Other things keep getting higher priority, so I really don't know when I'll get around to this

u6f6o commented 2 years ago

@geoand Is there any MR that I could use as a reference point? I was thinking about having a look and deciding whether I feel capable to create a MR on my own.

geoand commented 2 years ago

https://github.com/quarkusio/quarkus/pull/24741 is probably somewhat similar

enenuki commented 1 year ago

Any progress on this?

geoand commented 1 year ago

Not yet

feczkob commented 4 months ago

Any progress perhaps?:)

mschorsch commented 4 months ago

@Ladicek made recently great progress to simplify the support of kotlin coroutines support in Quarkus itself (https://github.com/quarkusio/quarkus/issues/26728#issuecomment-1993762397; https://github.com/quarkusio/quarkus/pull/40815). Maybe this info helps in implementing this feature.

elgabbouch commented 1 month ago

Any update?