https://bz.apache.org/bugzilla/show_bug.cgi?id=66875
Bug ID: 66875 Summary: Handling async error after spring already handled error Product: Tomcat 10 Version: 10.1.11 Hardware: All OS: All Status: NEW Severity: normal Priority: P2 Component: Catalina Assignee: dev@tomcat.apache.org Reporter: nils.ko...@gmail.com Target Milestone: ------ First reported as bug on Spring Boot(https://github.com/spring-projects/spring-boot/issues/36803). After some investigation I've been redirected by the spring team to post the bug here. Reproduction steps with spring boot(tested on version 3.0.0 and 3.1.2): - Create spring initializr project with Kotlin and spring-boot-starter-web - Add org.jetbrains.kotlinx:kotlinx-coroutines-reactor dependency - Run following (Kotlin) code --- @SpringBootApplication class DemoApplication fun main(args: Array<String>) { runApplication<DemoApplication>(*args) } @RestController class Controller { @GetMapping("suspend") suspend fun errorScenario() { throw IllegalStateException("Double exception handling") } } @ControllerAdvice class ExceptionHandler : ResponseEntityExceptionHandler() { @ExceptionHandler(Exception::class) fun handleUnexpectedException( ex: Exception, request: WebRequest ): ResponseEntity<Any>? { return handleException( ErrorResponseException( HttpStatus.INTERNAL_SERVER_ERROR, ProblemDetail.forStatus(500), ex ), request ) } } --- When calling the /suspend endpoint (with postman) spring will catch the exception and create a response. If that response is has status 500 it will also set "jakarta.servlet.error.exception" with the caught exception. Because of this, this code in org.apache.catalina.connector.CoyoteAdapter.asyncDispatch(...) will trigger a second exception handling. --- if (request.isAsyncDispatching()) { connector.getService().getContainer().getPipeline().getFirst().invoke(request, response); Throwable t = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION); if (t != null) { asyncConImpl.setErrorState(t, true); } } --- The result of this is that two response bodies, including body size, will be generated and returned. Postman will show that the request had a problem. This is because there is more data than the first body size reports, which is an invalid http response. Sniffing the response you can actually see the full response: --- HTTP/1.1 500 Content-Type: application/problem+json Transfer-Encoding: chunked Date: Tue, 08 Aug 2023 09:26:30 GMT Connection: close 59 {"type":"about:blank","title":"Internal Server Error","status":500,"instance":"/suspend"} 6c {"timestamp":"2023-08-08T09:26:30.705+00:00","status":500,"error":"Internal Server Error","path":"/suspend"} --- When calling the endpoint with chrome it will generate following error message, but only return a single response: s.e.ErrorMvcAutoConfiguration$StaticView : Cannot render error page for request [/suspend] as the response has already been committed. As a result, the response may have the wrong status code. This bug goes for me far too deep into the inner workings and I don't know if this is a tomcat bug or spring should just not set the the request attribute "jakarta.servlet.error.exception". It is however notable that when using another server(jetty, undertow) underneath spring this issue does not occur. And that is probably why the spring team send me here. -- You are receiving this mail because: You are the assignee for the bug. --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org