Well, this is an exceptional behavior, one needed for our use-case. We are using handler interceptor to "increment active request count" at "preHandle" method. On "afterCompletion" method we decrement the active request counter. So far so good. Sync calls are straight forward. But in case of async there is a 2nd request (DispatcherType: ASYNC) which is being used to decrement the counter while the main request (DispatcherType: REQUEST) is used to increment the counter. We check the dispatcher type to avoid double increment.So far so good.
The problem happening in case of some problematic clients, which disconnects after firing the request. In such a case while the main request enters the server but before the async thread starts the client disconnect itself (like close the browser).In this case the 2nd request (DispatcherType: ASYNC) not getting created at all. This situation leaving the counter increased (by the main request). For our use-case, we have to decrement the counter no matter what if it is incremented for a request. Looking forward to your help/suggestion here. Thanks in advance.
Other details: Spring Boot application Spring framework: 4.3.4.RELEASE Tomcat : 7 Using RestController Handler Interceptor: AsyncHandlerInterceptor In the asynchronous mode we use ResponseBodyEmitter to send data to client
Log in server:
Exception in thread "pool-87-thread-1" java.lang.IllegalStateException: The request associated with the **AsyncContext has already completed processing**.
at org.apache.catalina.core.AsyncContextImpl.check(AsyncContextImpl.java:497)
at org.apache.catalina.core.AsyncContextImpl.getRequest(AsyncContextImpl.java:209)
at org.apache.catalina.core.AsyncContextImpl.dispatch(AsyncContextImpl.java:198)
at org.apache.catalina.core.AsyncContextImpl.dispatch(AsyncContextImpl.java:170)
at org.apache.catalina.core.AsyncContextImpl.dispatch(AsyncContextImpl.java:164)
at org.springframework.web.context.request.async.StandardServletAsyncWebRequest.dispatch(StandardServletAsyncWebRequest.java:123)
at org.springframework.web.context.request.async.WebAsyncManager.setConcurrentResultAndDispatch(WebAsyncManager.java:353)
at org.springframework.web.context.request.async.WebAsyncManager.access$200(WebAsyncManager.java:58)
at org.springframework.web.context.request.async.WebAsyncManager$7.handleResult(WebAsyncManager.java:416)
at org.springframework.web.context.request.async.DeferredResult.setResultInternal(DeferredResult.java:199)
at org.springframework.web.context.request.async.DeferredResult.setErrorResult(DeferredResult.java:214)
at org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitterReturnValueHandler$HttpMessageConvertingHandler.completeWithError(ResponseBodyEmitterReturnValueHandler.java:219)
at org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter.completeWithError(ResponseBodyEmitter.java:204)
at org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter.sendInternal(ResponseBodyEmitter.java:169)
at org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter.send(ResponseBodyEmitter.java:159)
at net.atpco.pipeline.common.post.KryoResponseEmitterPostBox.send(KryoResponseEmitterPostBox.java:48)
at net.atpco.pipeline.common.post.KryoResponseEmitterPostBox.lambda$0(KryoResponseEmitterPostBox.java:37)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Updates: On further research I found this in spring documentation... "Note that HandlerInterceptor implementations may need to do work when an async request times out or completes with a network error. For such cases the Servlet container does not dispatch and therefore the postHandle and afterCompletion methods will not be invoked. Instead, interceptors can register to track an asynchronous request through the registerCallbackInterceptor and registerDeferredResultInterceptor methods on WebAsyncManager. This can be done proactively on every request from preHandle regardless of whether async request processing will start." This deferredResultInterceptor seems like solving the issue.