Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Quarkus cache does not work with kotlin suspend functions #23746

Open
u6f6o opened this issue Feb 16, 2022 · 24 comments
Open

Quarkus cache does not work with kotlin suspend functions #23746

u6f6o opened this issue Feb 16, 2022 · 24 comments
Labels

Comments

@u6f6o
Copy link

u6f6o commented Feb 16, 2022

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

@u6f6o u6f6o added the kind/bug Something isn't working label Feb 16, 2022
@quarkus-bot
Copy link

quarkus-bot bot commented Feb 16, 2022

/cc @evanchooly, @gwenneg

@u6f6o
Copy link
Author

u6f6o commented Feb 16, 2022

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
Copy link
Member

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
Copy link
Author

u6f6o commented Mar 14, 2022

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
Copy link
Member

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
Copy link
Member

gwenneg commented Mar 17, 2022

I can help with that @evanchooly.

@geoand
Copy link
Contributor

geoand commented Apr 12, 2022

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

@gwenneg
Copy link
Member

gwenneg commented Apr 12, 2022

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
Copy link
Contributor

geoand commented Apr 12, 2022

@evanchooly have you started looking into this?

@gwenneg
Copy link
Member

gwenneg commented Apr 12, 2022

This issue and #21592 could be related.

@evanchooly
Copy link
Member

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

@geoand
Copy link
Contributor

geoand commented Apr 12, 2022

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

@stephan-strate
Copy link
Contributor

Any updates on this issue?

@geoand
Copy link
Contributor

geoand commented Aug 18, 2022

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

@mschorsch
Copy link
Contributor

@geoand Any updates? Maybe for 2.13 😄 ?

@geoand
Copy link
Contributor

geoand commented Sep 15, 2022

Not a chance :)

@geoand
Copy link
Contributor

geoand commented Sep 15, 2022

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

@u6f6o
Copy link
Author

u6f6o commented Oct 19, 2022

@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
Copy link
Contributor

geoand commented Oct 19, 2022

#24741 is probably somewhat similar

@enenuki
Copy link

enenuki commented May 29, 2023

Any progress on this?

@geoand
Copy link
Contributor

geoand commented May 29, 2023

Not yet

@feczkob
Copy link

feczkob commented Jun 16, 2024

Any progress perhaps?:)

@mschorsch
Copy link
Contributor

mschorsch commented Jun 16, 2024

@Ladicek made recently great progress to simplify the support of kotlin coroutines support in Quarkus itself (#26728 (comment); #40815). Maybe this info helps in implementing this feature.

@elgabbouch
Copy link

Any update?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

9 participants