quarkus: Reactive Client ResponseExceptionMapper throws BlockingNotAllowedException when consuming an InputStream

Describe the bug

When trying to read the response entity in an ResponseExceptionMapper, a BlockingNotAllowedException is thrown IF the return type of the call is an InputStream. It works fine with String.

My Application subclass is annotated @Blocking and I can also do blocking operations like a Thread.sleep in the ExceptionMapper

Expected behavior

InputStreams can be handled

Actual behavior

Using response.readEntity(JsonNode.class) in a ResponseExceptionMapper throws a BlockingNotAllowedException when the response type is InputStream

How to Reproduce?

see https://github.com/ssaip/quarkus-reactive-client-inputstream-blocking

org.jboss.resteasy.reactive.common.core.BlockingNotAllowedException: Attempting a blocking read on io thread
	at org.jboss.resteasy.reactive.client.handlers.VertxClientInputStream$VertxBlockingInput.readBlocking(VertxClientInputStream.java:199)
	at org.jboss.resteasy.reactive.client.handlers.VertxClientInputStream.readIntoBuffer(VertxClientInputStream.java:81)
	at org.jboss.resteasy.reactive.client.handlers.VertxClientInputStream.read(VertxClientInputStream.java:62)
	at com.fasterxml.jackson.core.json.ByteSourceJsonBootstrapper.ensureLoaded(ByteSourceJsonBootstrapper.java:539)
	at com.fasterxml.jackson.core.json.ByteSourceJsonBootstrapper.detectEncoding(ByteSourceJsonBootstrapper.java:133)
	at com.fasterxml.jackson.core.json.ByteSourceJsonBootstrapper.constructParser(ByteSourceJsonBootstrapper.java:256)
	at com.fasterxml.jackson.core.JsonFactory._createParser(JsonFactory.java:1685)
	at com.fasterxml.jackson.core.JsonFactory.createParser(JsonFactory.java:1084)
	at com.fasterxml.jackson.databind.ObjectReader.createParser(ObjectReader.java:1103)
	at com.fasterxml.jackson.databind.ObjectReader.readValue(ObjectReader.java:1481)
	at io.quarkus.resteasy.reactive.jackson.runtime.serialisers.ServerJacksonMessageBodyReader.doReadFrom(ServerJacksonMessageBodyReader.java:87)
	at io.quarkus.resteasy.reactive.jackson.runtime.serialisers.ServerJacksonMessageBodyReader.readFrom(ServerJacksonMessageBodyReader.java:38)
	at org.jboss.resteasy.reactive.client.impl.ClientReaderInterceptorContextImpl.proceed(ClientReaderInterceptorContextImpl.java:67)
	at org.jboss.resteasy.reactive.client.impl.ClientSerialisers.invokeClientReader(ClientSerialisers.java:160)
	at org.jboss.resteasy.reactive.client.impl.ClientResponseImpl.readEntity(ClientResponseImpl.java:59)
	at org.jboss.resteasy.reactive.common.jaxrs.ResponseImpl.readEntity(ResponseImpl.java:129)
	at com.example.BlockingResponseExceptionMapper.toThrowable(BlockingResponseExceptionMapper.java:24)
	at com.example.BlockingResponseExceptionMapper.toThrowable(BlockingResponseExceptionMapper.java:10)
	at com.example.BlockingResponseExceptionMapper_ClientProxy.toThrowable(Unknown Source)
	at io.quarkus.rest.client.reactive.runtime.MicroProfileRestClientResponseFilter.filter(MicroProfileRestClientResponseFilter.java:36)
	at org.jboss.resteasy.reactive.client.handlers.ClientResponseFilterRestHandler.handle(ClientResponseFilterRestHandler.java:21)
	at org.jboss.resteasy.reactive.client.handlers.ClientResponseFilterRestHandler.handle(ClientResponseFilterRestHandler.java:10)
	at org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext.invokeHandler(AbstractResteasyReactiveContext.java:229)
	at org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext.run(AbstractResteasyReactiveContext.java:145)
	at org.jboss.resteasy.reactive.client.impl.RestClientRequestContext$1.lambda$execute$0(RestClientRequestContext.java:279)
	at io.vertx.core.impl.ContextInternal.dispatch(ContextInternal.java:264)
	at io.vertx.core.impl.ContextInternal.dispatch(ContextInternal.java:246)
	at io.vertx.core.impl.EventLoopContext.lambda$runOnContext$0(EventLoopContext.java:43)
	at io.netty.util.concurrent.AbstractEventExecutor.runTask(AbstractEventExecutor.java:174)
	at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:167)
	at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:470)
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:566)
	at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997)
	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)

Output of uname -a or ver

No response

Output of java -version

Java version: 11.0.17, vendor: Azul Systems, Inc.

GraalVM version (if different from Java)

No response

Quarkus version or git rev

2.15.3.Final

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

apache-maven-3.8.6

Additional information

No response

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Comments: 29 (18 by maintainers)

Commits related to this issue

Most upvoted comments

While this was working in my sample project, I still couldn’t get it to work in the real project. I’ll keep this here and maybe it saves someone else some hours in the future.

I figured out that my ResponseExceptionMapper always got _subclass’d because I was using jakarta.validation annotations there and had the io.quarkus:quarkus-hibernate-validator dependency on the classpath, therefore this class was always getting proxied to intercept for validations.

And because of that, the @Blocking annotation was no longer present and the ResponseExceptionMapper was run on the io thread. Unfortunately, there is no way to fix the annotations for sub/proxied classes (a topic that I originally came across when investigating the cause of this issue 😄 see https://github.com/quarkusio/quarkus/issues/30327)

Anyway, removing annotations like @NotNull, will prevent subclassing and the @Blocking ResponseExceptionMapper will run on a worker-thread as expected.

This is caused because the exception mapper is a bean (it’s using the @ApplicationScoped annotation). This can be easily fixed. Tho, @ssaip if you replace @ApplicationScoped with @Provider as noted in the guide, it will work fine.

Okay. Asking because this issue is specific to the client.

Please open a new issue and attach a sample application that exhibits the problem you are seeing in the server which although looks similar, has a different root cause and is likely easy to fix).

We didn’t change anything, so, for sure, the bug is still there.

Unfortunately, I don’t see a good approach to fix it, except requiring an @Blocking annotation (or a blocking mapper interface) and delegating the invocation of this mapper on a worker thread.

@Sgitario @geoand WDYT?