quarkusio / quarkus

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

Redis Cache doesn't run in worker thread #42253

Closed zche-theScore closed 3 weeks ago

zche-theScore commented 1 month ago

Describe the bug

When I tried to use Redis Cache @CacheResult() in a GraphQL resolver which runs in a worker thread, it hangs and gives and error

Thread Thread[vert.x-worker-thread-3,5,main] has been blocked for 60427 ms, time limit is 60000 ms: io.vertx.core.VertxException: Thread blocked

There's no problem if I switch the GraphQL resolver to using Uni which makes it run in a event-loop thread. There's no problem if I used Caffeine Cache. There's no problem if there exists the cache entry in Redis.

Reproducer: https://github.com/zche-theScore/quarkus-redis-cache-blocking

Expected behavior

I didn't see any note saying Redis Cache can only be used on event-loop thread in the docs.

Actual behavior

Redis Cache @CacheResult() makes a worker thread hang.

How to Reproduce?

Reproducer: https://github.com/zche-theScore/quarkus-redis-cache-blocking

Output of uname -a or ver

Darwin ca-on-a-zche.local 23.3.0 Darwin Kernel Version 23.3.0: Wed Dec 20 21:30:44 PST 2023; root:xnu-10002.81.5~7/RELEASE_ARM64_T6000 arm64

Output of java -version

openjdk 22.0.1 2024-04-16 OpenJDK Runtime Environment (build 22.0.1+8-16) OpenJDK 64-Bit Server VM (build 22.0.1+8-16, mixed mode, sharing)

Quarkus version or git rev

3.13.0

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

Apache Maven 3.9.8 (36645f6c9b5079805ea5009217e36f2cffd34256)

Additional information

No response

quarkus-bot[bot] commented 1 month ago

/cc @Ladicek (redis), @cescoffier (redis), @gwenneg (cache), @machi1990 (redis)

Ladicek commented 1 month ago

I've been looking at this today and while I didn't conclude yet, I found one interesting thing: there's only one Vert.x worker thread in the pool. That thread waits for loading the value, which should also happen on the worker thread pool.

I tried setting quarkus.thread-pool.core-threads=8, but that creates 8 different threads that are not used as Vert.x worker threads, so maybe there's an issue there. Not yet sure what.

Ladicek commented 4 weeks ago

OK, so when I run the app in the prod mode, where the Vert.x worker thread pool and the Quarkus thread pool indeed are identical, it still doesn't work.

Then, I tried replacing the GraphQL resolver with a JAX-RS resource and it works just fine. That suggests that SmallRye GraphQL's execution model is perhaps somewhat different than RESTEasy Reactive's.

I looked further and found that the problem might be in that the Redis cache implementation uses an ordered worker submission. That is, it requests that both actions are executed on the same worker thread. Not sure why that works fine with JAX-RS and doesn't work with GraphQL...

Ladicek commented 4 weeks ago

OK, so I have figured out a workaround -- configure

quarkus.smallrye-graphql.nonblocking.enabled=false

Why does that make it work? The method is clearly blocking, so what's the difference?

The difference is that with quarkus.smallrye-graphql.nonblocking.enabled=false, the GraphQL route is registered as blocking, which essentially boils down to BlockingHandlerDecorator, which is created with the ordered parameter set to false. On the other hand, in the default configuration, the GraphQL route is registered as non-blocking and the blocking is done in BlockingHelper.runBlocking, which doesn't pass the ordered parameter, which is equivalent to passing true.

It seems to me that this should pass false explicitly -- then it works.