-
Bug
-
Resolution: Done
-
Blocker
-
None
-
None
Customer is moving a spring app from netty to EAP 7 and it hits a new UT000146 exception on EAP 7. They provided a test app that reproduces and this shows spring starts an async request:
2020-04-14 12:10:09,992 INFO [stdout] (default task-1) --------------------------->HttpServerExchange.dispatch HttpServerExchange{ POST /testApp/api/v1.0/test} io.undertow.util.SameThreadExecutor@514a0839 io.undertow.servlet.spec.AsyncContextImpl$1@3204dd1c 2020-04-14 12:10:09,992 INFO [stdout] (default task-1) io.undertow.server.HttpServerExchange.dispatch(HttpServerExchange.java:-1) 2020-04-14 12:10:09,992 INFO [stdout] (default task-1) io.undertow.servlet.spec.AsyncContextImpl.<init>(AsyncContextImpl.java:109) 2020-04-14 12:10:09,992 INFO [stdout] (default task-1) io.undertow.servlet.spec.HttpServletRequestImpl.startAsync(HttpServletRequestImpl.java:1036) 2020-04-14 12:10:09,992 INFO [stdout] (default task-1) org.springframework.http.server.reactive.ServletHttpHandlerAdapter.service(ServletHttpHandlerAdapter.java:166)
And then right after adds a read listener to the ServletInputStream:
2020-04-14 12:10:10,079 INFO [stdout] (default task-1) --------------------------->ServletInputStream.setReadListener org.springframework.http.server.reactive.ServletServerHttpRequest$RequestBodyPublisher$RequestBodyPublisherReadListener@e40187 2020-04-14 12:10:10,079 INFO [stdout] (default task-1) io.undertow.servlet.spec.ServletInputStreamImpl.setReadListener(ServletInputStreamImpl.java:140) 2020-04-14 12:10:10,080 INFO [stdout] (default task-1) org.springframework.http.server.reactive.ServletServerHttpRequest$RequestBodyPublisher.registerReadListener(ServletServerHttpRequest.java:282) 2020-04-14 12:10:10,080 INFO [stdout] (default task-1) org.springframework.http.server.reactive.ServletServerHttpRequest.<init>(ServletServerHttpRequest.java:98) 2020-04-14 12:10:10,080 INFO [stdout] (default task-1) org.springframework.http.server.reactive.ServletServerHttpRequest.<init>(ServletServerHttpRequest.java:77) 2020-04-14 12:10:10,080 INFO [stdout] (default task-1) org.springframework.http.server.reactive.ServletHttpHandlerAdapter.createRequest(ServletHttpHandlerAdapter.java:200) 2020-04-14 12:10:10,080 INFO [stdout] (default task-1) org.springframework.http.server.reactive.ServletHttpHandlerAdapter.service(ServletHttpHandlerAdapter.java:171) 2020-04-14 12:10:10,080 INFO [stdout] (default task-1) io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74) 2020-04-14 12:10:10,080 INFO [stdout] (default task-1) io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62) 2020-04-14 12:10:10,080 INFO [stdout] (default task-1) io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:68) 2020-04-14 12:10:10,080 INFO [stdout] (default task-1) io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)
It then lets the request go to more reactor.core application code, resulting in a ServletInputStreamImpl.isReady call. Undertow throws this exception as a result since it treats this as a resume after a dispatch within the same request cycle since the app never let the request processing fall out to some actual async processing:
2020-04-14 12:10:10,238 ERROR [org.springframework.web.server.adapter.HttpWebHandlerAdapter] (default task-1) [42d4430f] 500 Server Error for HTTP POST "/testApp/api/v1.0/test": java.lang.IllegalStateException: UT000146: HttpServerExchange cannot have both async IO resumed and dispatch() called in the same cycle at io.undertow.server.HttpServerExchange$ReadDispatchChannel.resumeReads(HttpServerExchange.java:2142) Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: Error has been observed at the following site(s): |_ checkpoint ⇢ HTTP POST "/testApp/api/v1.0/test" [ExceptionHandlingWebHandler] Stack trace: at io.undertow.server.HttpServerExchange$ReadDispatchChannel.resumeReads(HttpServerExchange.java:2142) at io.undertow.servlet.spec.ServletInputStreamImpl.isReady(ServletInputStreamImpl.java:104) at org.springframework.http.server.reactive.ServletServerHttpRequest$RequestBodyPublisher.checkOnDataAvailable(ServletServerHttpRequest.java:287) at org.springframework.http.server.reactive.AbstractListenerReadPublisher.changeToDemandState(AbstractListenerReadPublisher.java:222) at org.springframework.http.server.reactive.AbstractListenerReadPublisher.access$1000(AbstractListenerReadPublisher.java:48) at org.springframework.http.server.reactive.AbstractListenerReadPublisher$State$2.request(AbstractListenerReadPublisher.java:333) at org.springframework.http.server.reactive.AbstractListenerReadPublisher$ReadSubscription.request(AbstractListenerReadPublisher.java:260) at reactor.core.publisher.MonoCollect$CollectSubscriber.onSubscribe(MonoCollect.java:120) at org.springframework.http.server.reactive.AbstractListenerReadPublisher$State$1.subscribe(AbstractListenerReadPublisher.java:301) at org.springframework.http.server.reactive.AbstractListenerReadPublisher.subscribe(AbstractListenerReadPublisher.java:105) at reactor.core.publisher.FluxSource.subscribe(FluxSource.java:58)
So seeing that, I could boil this down to a much simpler async servlet implementation that reproduces this with just those 3 noted operations:
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { AsyncContext asyncCtx = req.startAsync(); req.getInputStream().setReadListener(new MyReadListener(asyncCtx, resp, req.getInputStream())); System.out.println(req.getInputStream().isReady()); }
That reproducer is attached for your reference. After deploying the helloworld.war, it can be tested like so:
curl -v -X POST -d 'test' localhost:8080/helloworld/HelloWorld
Testing elsewhere like on Tomcat 9, I note no issue as well. So is Undertow incorrect to force this error?
- is cloned by
-
JBEAP-19245 [GSS](7.3.z) UNDERTOW-1683 - UT000146 is improperly thrown
- Closed