quarkusio / quarkus

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

Unpredictable reactive controller behavior. #21120

Closed kazik666 closed 2 years ago

kazik666 commented 2 years ago

Describe the bug

I decided to refactor one of my service written in quarkus from blocking io to reactive manner.

I have many mock controller which works perfectly before refactor but now some of them throws errors:

java.lang.NullPointerException: Cannot invoke "org.jboss.resteasy.reactive.server.mapping.RuntimeResource.getResourceClass()" because the return value of "org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext.getTarget()" is null
    at org.jboss.resteasy.reactive.server.core.ExceptionMapping.logBlockingErrorIfRequired(ExceptionMapping.java:103)
    at org.jboss.resteasy.reactive.server.core.ExceptionMapping.mapException(ExceptionMapping.java:95)
    at org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext.mapExceptionIfPresent(ResteasyReactiveRequestContext.java:340)
    at org.jboss.resteasy.reactive.server.handlers.ExceptionHandler.handle(ExceptionHandler.java:15)
    at org.jboss.resteasy.reactive.server.handlers.ExceptionHandler.handle(ExceptionHandler.java:9)
    at org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext.run(AbstractResteasyReactiveContext.java:141)
    at org.jboss.resteasy.reactive.server.handlers.RestInitialHandler.beginProcessing(RestInitialHandler.java:47)
    at org.jboss.resteasy.reactive.server.vertx.ResteasyReactiveVertxHandler.handle(ResteasyReactiveVertxHandler.java:17)
    at org.jboss.resteasy.reactive.server.vertx.ResteasyReactiveVertxHandler.handle(ResteasyReactiveVertxHandler.java:7)
    at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1127)
    at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:151)
    at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:133)
    at io.quarkus.vertx.http.runtime.StaticResourcesRecorder.lambda$start$1(StaticResourcesRecorder.java:67)
    at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1127)
    at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:151)
    at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:133)
    at io.quarkus.vertx.http.runtime.VertxHttpRecorder$5.handle(VertxHttpRecorder.java:351)
    at io.quarkus.vertx.http.runtime.VertxHttpRecorder$5.handle(VertxHttpRecorder.java:329)
    at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1127)
    at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:151)
    at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:133)
    at io.vertx.ext.web.impl.RouterImpl.handle(RouterImpl.java:55)
    at io.vertx.ext.web.impl.RouterImpl.handle(RouterImpl.java:37)
    at io.quarkus.vertx.http.runtime.VertxHttpRecorder$10.handle(VertxHttpRecorder.java:443)
    at io.quarkus.vertx.http.runtime.VertxHttpRecorder$10.handle(VertxHttpRecorder.java:440)
    at io.quarkus.vertx.http.runtime.VertxHttpRecorder$1.handle(VertxHttpRecorder.java:151)
    at io.quarkus.vertx.http.runtime.VertxHttpRecorder$1.handle(VertxHttpRecorder.java:133)
    at io.vertx.core.http.impl.Http1xServerRequestHandler.handle(Http1xServerRequestHandler.java:67)
    at io.vertx.core.http.impl.Http1xServerRequestHandler.handle(Http1xServerRequestHandler.java:30)
    at io.vertx.core.impl.EventLoopContext.emit(EventLoopContext.java:50)
    at io.vertx.core.impl.DuplicatedContext.emit(DuplicatedContext.java:168)
    at io.vertx.core.http.impl.Http1xServerConnection.handleMessage(Http1xServerConnection.java:143)
    at io.vertx.core.net.impl.ConnectionBase.read(ConnectionBase.java:155)
    at io.vertx.core.net.impl.VertxHandler.channelRead(VertxHandler.java:154)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
    at io.netty.channel.ChannelInboundHandlerAdapter.channelRead(ChannelInboundHandlerAdapter.java:93)
    at io.netty.handler.codec.http.websocketx.extensions.WebSocketServerExtensionHandler.channelRead(WebSocketServerExtensionHandler.java:99)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
    at io.vertx.core.http.impl.Http1xUpgradeToH2CHandler.channelRead(Http1xUpgradeToH2CHandler.java:116)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
    at io.netty.handler.timeout.IdleStateHandler.channelRead(IdleStateHandler.java:286)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
    at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:324)
    at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:311)
    at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:432)
    at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:276)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
    at io.vertx.core.http.impl.Http1xOrH2CHandler.end(Http1xOrH2CHandler.java:61)
    at io.vertx.core.http.impl.Http1xOrH2CHandler.channelRead(Http1xOrH2CHandler.java:38)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
    at io.netty.handler.timeout.IdleStateHandler.channelRead(IdleStateHandler.java:286)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
    at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
    at io.netty.channel.epoll.AbstractEpollStreamChannel$EpollStreamUnsafe.epollInReady(AbstractEpollStreamChannel.java:795)
    at io.netty.channel.epoll.EpollEventLoop.processReady(EpollEventLoop.java:480)
    at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:378)
    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:831)

For example this controller doesn't work:

package pl.test.rest.recaptcha;

import io.smallrye.mutiny.Uni;
import pl.test.rest.recaptcha.model.SiteVerifyResponse;

import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import java.util.Date;

@Path("/recaptcha/api/siteverify")
public class MockResource {
    @POST
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    public Uni<SiteVerifyResponse> siteVerify(@FormParam("response") String response
            , @FormParam("secret") String secret, @FormParam("remoteip") String remoteip) {
        return Uni.createFrom().item(SiteVerifyResponse.builder()
                .success(true)
                .score((float) 1.0)
                .action("ACTION")
                .hostname("localhost")
                .challengeTs(new Date())
                .build());
    }
}

But this work's:

package pl.test.rest.dotpay;

import io.smallrye.mutiny.Uni;
import lombok.val;
import pl.test.rest.dotpay.model.PaymentEntry;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import java.util.Collections;
import java.util.List;

@Path("/s2/login/api/purchase_history/")
public class MockResource {

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Uni<List<PaymentEntry>> api(@QueryParam("email") String email) {
        return Uni.createFrom().item(Collections.singletonList(createPaymentEntry()));
    }

    private PaymentEntry createPaymentEntry() {
        val entry = new PaymentEntry();
        entry.setDate("2018-06-09T16:54:15.855823");
        entry.setAmount("8.29");
        entry.setCurrency("PLN");
        entry.setStatus("completed");
        entry.setRequestIp("188.146.162.128");
        entry.setMcc("4112");
        entry.setChannel("73");
        entry.setMerchantId("5d51e2dc7bc76cf9841d82ba404d134646bdbd97");

        return entry;
    }
}

The one thing what I'am done in refactor is change the return type to new one with wrapper Uni.

I found on network similar error: https://github.com/quarkusio/quarkus/runs/3204982867

Expected behavior

No response

Actual behavior

No response

How to Reproduce?

No response

Output of uname -a or ver

Linux 5.13.0-20-generic #20-Ubuntu SMP Fri Oct 15 14:21:35 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

Output of java -version

16.0.2

GraalVM version (if different from Java)

No response

Quarkus version or git rev

2.4.0.Final

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

Apache Maven 3.6.2

Additional information

No response

geoand commented 2 years ago

Can you please attach a sample project?

kazik666 commented 2 years ago

Yes of course i'll create sample project.

geoand commented 2 years ago

Thanks.

In any case, https://github.com/quarkusio/quarkus/pull/21121 will remove the NPE

kazik666 commented 2 years ago

I created demo project: https://github.com/kazik666/quarkus-reactive-demo

But in this project I doesn't have error. I try to add rest dependencies a reproduce orginal error.

kazik666 commented 2 years ago

@geoand I reproduced this error. It occurred when I'am adding filters.

kazik666 commented 2 years ago

I found the problem :) In filter i used blocking IO: org.jboss.resteasy.reactive.common.core.BlockingNotAllowedException: Attempting a blocking read on io thread

I must re-implement my filters.

kazik666 commented 2 years ago

I found on StackOverFlow that kind of filter is impossible to implement: https://stackoverflow.com/questions/67565473/logging-entity-body-with-quarkus-resteasy-reactive

@geoand This is still true in the newest version of quarkus?

geoand commented 2 years ago

If you are trying to read the entire body of the request on the event loop thread, then yes, that is still not allowed

kazik666 commented 2 years ago

Ok I'am closing this issue.

JohnnMartins commented 2 years ago

If you are trying to read the entire body of the request on the event loop thread, then yes, that is still not allowed

Why it is not allowed yet? and is there any chance it is allowed?