Ensure async Callables are in sync with the call stack
After this change each call stack level pushes and pops an async Callable to ensure the AsyncExecutionChain is in sync with the call stack. Before this change, a controller returning a "forward:" prefixed string caused the AsyncExecutionChain to contain a extra Callables that did not match the actual call stack. Issue: SPR-9611
This commit is contained in:
@@ -64,19 +64,17 @@ public interface AsyncHandlerInterceptor extends HandlerInterceptor {
|
||||
AbstractDelegatingCallable getAsyncCallable(HttpServletRequest request, HttpServletResponse response, Object handler);
|
||||
|
||||
/**
|
||||
* Invoked <em>after</em> the execution of a handler if the handler started
|
||||
* Invoked <em>after</em> the execution of a handler but only if the handler started
|
||||
* async processing instead of handling the request. Effectively this method
|
||||
* is invoked on the way out of the main processing thread instead of
|
||||
* {@link #postHandle(WebRequest, org.springframework.ui.ModelMap)}. The
|
||||
* <code>postHandle</code> method is invoked after the request is handled
|
||||
* in the async thread.
|
||||
* <p>Implementations of this method can ensure ThreadLocal attributes bound
|
||||
* to the main thread are cleared and also prepare for binding them to the
|
||||
* async thread.
|
||||
* is invoked instead of {@link #postHandle(WebRequest, org.springframework.ui.ModelMap)}
|
||||
* on the way out of the main processing thread allowing implementations
|
||||
* to ensure ThreadLocal attributes are cleared. The <code>postHandle</code>
|
||||
* invocation is effectively delayed until after async processing when the
|
||||
* request has actually been handled.
|
||||
* @param request current HTTP request
|
||||
* @param response current HTTP response
|
||||
* @param handler chosen handler to execute, for type and/or instance examination
|
||||
*/
|
||||
void postHandleAsyncStarted(HttpServletRequest request, HttpServletResponse response, Object handler);
|
||||
void postHandleAfterAsyncStarted(HttpServletRequest request, HttpServletResponse response, Object handler);
|
||||
|
||||
}
|
||||
|
||||
@@ -817,8 +817,6 @@ public class DispatcherServlet extends FrameworkServlet {
|
||||
@Override
|
||||
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
|
||||
AsyncExecutionChain asyncChain = AsyncExecutionChain.getForCurrentRequest(request);
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
String requestUri = urlPathHelper.getRequestUri(request);
|
||||
logger.debug("DispatcherServlet with name '" + getServletName() + "' processing " + request.getMethod() +
|
||||
@@ -853,13 +851,14 @@ public class DispatcherServlet extends FrameworkServlet {
|
||||
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
|
||||
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
|
||||
|
||||
asyncChain.addDelegatingCallable(getServiceAsyncCallable(request, attributesSnapshot));
|
||||
AsyncExecutionChain asyncChain = AsyncExecutionChain.getForCurrentRequest(request);
|
||||
asyncChain.push(getServiceAsyncCallable(request, attributesSnapshot));
|
||||
|
||||
try {
|
||||
doDispatch(request, response);
|
||||
}
|
||||
finally {
|
||||
if (asyncChain.isAsyncStarted()) {
|
||||
if (!asyncChain.pop()) {
|
||||
return;
|
||||
}
|
||||
// Restore the original attribute snapshot, in case of an include.
|
||||
@@ -881,7 +880,7 @@ public class DispatcherServlet extends FrameworkServlet {
|
||||
logger.debug("Resuming asynchronous processing of " + request.getMethod() +
|
||||
" request for [" + urlPathHelper.getRequestUri(request) + "]");
|
||||
}
|
||||
getNextCallable().call();
|
||||
getNext().call();
|
||||
if (attributesSnapshot != null) {
|
||||
restoreAttributesAfterInclude(request, attributesSnapshot);
|
||||
}
|
||||
@@ -904,7 +903,9 @@ public class DispatcherServlet extends FrameworkServlet {
|
||||
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
HttpServletRequest processedRequest = request;
|
||||
HandlerExecutionChain mappedHandler = null;
|
||||
|
||||
AsyncExecutionChain asyncChain = AsyncExecutionChain.getForCurrentRequest(request);
|
||||
boolean asyncStarted = false;
|
||||
|
||||
try {
|
||||
ModelAndView mv = null;
|
||||
@@ -941,22 +942,23 @@ public class DispatcherServlet extends FrameworkServlet {
|
||||
return;
|
||||
}
|
||||
|
||||
mappedHandler.addDelegatingCallables(processedRequest, response);
|
||||
mappedHandler.pushInterceptorCallables(processedRequest, response);
|
||||
asyncChain.push(getDispatchAsyncCallable(mappedHandler, request, response, processedRequest));
|
||||
|
||||
asyncChain.addDelegatingCallable(
|
||||
getDispatchAsyncCallable(mappedHandler, request, response, processedRequest));
|
||||
|
||||
// Actually invoke the handler.
|
||||
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
|
||||
|
||||
if (asyncChain.isAsyncStarted()) {
|
||||
mappedHandler.applyPostHandleAsyncStarted(processedRequest, response);
|
||||
logger.debug("Exiting request thread and leaving the response open");
|
||||
return;
|
||||
try {
|
||||
// Actually invoke the handler.
|
||||
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
|
||||
}
|
||||
finally {
|
||||
asyncStarted = !asyncChain.pop();
|
||||
mappedHandler.popInterceptorCallables(processedRequest, response, asyncStarted);
|
||||
if (asyncStarted) {
|
||||
logger.debug("Exiting request thread and leaving the response open");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
applyDefaultViewName(request, mv);
|
||||
|
||||
mappedHandler.applyPostHandle(processedRequest, response, mv);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
@@ -971,7 +973,7 @@ public class DispatcherServlet extends FrameworkServlet {
|
||||
triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
|
||||
}
|
||||
finally {
|
||||
if (asyncChain.isAsyncStarted()) {
|
||||
if (asyncStarted) {
|
||||
return;
|
||||
}
|
||||
// Clean up any resources used by a multipart request.
|
||||
@@ -1044,7 +1046,7 @@ public class DispatcherServlet extends FrameworkServlet {
|
||||
ModelAndView mv = null;
|
||||
Exception dispatchException = null;
|
||||
try {
|
||||
mv = (ModelAndView) getNextCallable().call();
|
||||
mv = (ModelAndView) getNext().call();
|
||||
applyDefaultViewName(processedRequest, mv);
|
||||
mappedHandler.applyPostHandle(request, response, mv);
|
||||
}
|
||||
|
||||
@@ -906,7 +906,7 @@ public abstract class FrameworkServlet extends HttpServletBean {
|
||||
initContextHolders(request, localeContext, requestAttributes);
|
||||
|
||||
AsyncExecutionChain chain = AsyncExecutionChain.getForCurrentRequest(request);
|
||||
chain.addDelegatingCallable(getAsyncCallable(startTime, request, response,
|
||||
chain.push(getAsyncCallable(startTime, request, response,
|
||||
previousLocaleContext, previousAttributes, localeContext, requestAttributes));
|
||||
|
||||
try {
|
||||
@@ -917,7 +917,7 @@ public abstract class FrameworkServlet extends HttpServletBean {
|
||||
}
|
||||
finally {
|
||||
resetContextHolders(request, previousLocaleContext, previousAttributes);
|
||||
if (chain.isAsyncStarted()) {
|
||||
if (!chain.pop()) {
|
||||
return;
|
||||
}
|
||||
finalizeProcessing(startTime, request, response, requestAttributes, failureCause);
|
||||
@@ -1018,7 +1018,7 @@ public abstract class FrameworkServlet extends HttpServletBean {
|
||||
initContextHolders(request, localeContext, requestAttributes);
|
||||
Throwable unhandledFailure = null;
|
||||
try {
|
||||
getNextCallable().call();
|
||||
getNext().call();
|
||||
}
|
||||
catch (Throwable t) {
|
||||
unhandledFailure = t;
|
||||
|
||||
@@ -49,6 +49,8 @@ public class HandlerExecutionChain {
|
||||
|
||||
private int interceptorIndex = -1;
|
||||
|
||||
private int pushedCallableCount;
|
||||
|
||||
/**
|
||||
* Create a new HandlerExecutionChain.
|
||||
* @param handler the handler object to execute
|
||||
@@ -124,9 +126,7 @@ public class HandlerExecutionChain {
|
||||
* next interceptor or the handler itself. Else, DispatcherServlet assumes
|
||||
* that this interceptor has already dealt with the response itself.
|
||||
*/
|
||||
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response)
|
||||
throws Exception {
|
||||
|
||||
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
if (getInterceptors() != null) {
|
||||
for (int i = 0; i < getInterceptors().length; i++) {
|
||||
HandlerInterceptor interceptor = getInterceptors()[i];
|
||||
@@ -140,12 +140,31 @@ public class HandlerExecutionChain {
|
||||
return true;
|
||||
}
|
||||
|
||||
void pushInterceptorCallables(HttpServletRequest request, HttpServletResponse response) {
|
||||
if (getInterceptors() == null) {
|
||||
return;
|
||||
}
|
||||
for (HandlerInterceptor interceptor : getInterceptors()) {
|
||||
if (interceptor instanceof AsyncHandlerInterceptor) {
|
||||
try {
|
||||
AsyncHandlerInterceptor asyncInterceptor = (AsyncHandlerInterceptor) interceptor;
|
||||
AbstractDelegatingCallable callable = asyncInterceptor.getAsyncCallable(request, response, this.handler);
|
||||
if (callable != null) {
|
||||
AsyncExecutionChain.getForCurrentRequest(request).push(callable);
|
||||
this.pushedCallableCount++;
|
||||
}
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
logger.error("HandlerInterceptor failed to return an async Callable", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply postHandle methods of registered interceptors.
|
||||
*/
|
||||
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv)
|
||||
throws Exception {
|
||||
|
||||
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
|
||||
if (getInterceptors() == null) {
|
||||
return;
|
||||
}
|
||||
@@ -156,50 +175,28 @@ public class HandlerExecutionChain {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add delegating, async Callable instances to the {@link AsyncExecutionChain}
|
||||
* for use in case of asynchronous request processing.
|
||||
* Remove pushed callables and apply postHandleAsyncStarted callbacks.
|
||||
*/
|
||||
void addDelegatingCallables(HttpServletRequest request, HttpServletResponse response)
|
||||
throws Exception {
|
||||
void popInterceptorCallables(HttpServletRequest request, HttpServletResponse response,
|
||||
boolean asyncStarted) throws Exception {
|
||||
|
||||
if (getInterceptors() == null) {
|
||||
return;
|
||||
}
|
||||
for (int i = getInterceptors().length - 1; i >= 0; i--) {
|
||||
HandlerInterceptor interceptor = getInterceptors()[i];
|
||||
if (interceptor instanceof AsyncHandlerInterceptor) {
|
||||
try {
|
||||
AsyncHandlerInterceptor asyncInterceptor = (AsyncHandlerInterceptor) interceptor;
|
||||
AsyncExecutionChain chain = AsyncExecutionChain.getForCurrentRequest(request);
|
||||
AbstractDelegatingCallable callable = asyncInterceptor.getAsyncCallable(request, response, this.handler);
|
||||
if (callable != null) {
|
||||
chain.addDelegatingCallable(callable);
|
||||
AsyncExecutionChain chain = AsyncExecutionChain.getForCurrentRequest(request);
|
||||
for ( ; this.pushedCallableCount > 0; this.pushedCallableCount--) {
|
||||
chain.pop();
|
||||
}
|
||||
if (asyncStarted) {
|
||||
for (int i = getInterceptors().length - 1; i >= 0; i--) {
|
||||
HandlerInterceptor interceptor = getInterceptors()[i];
|
||||
if (interceptor instanceof AsyncHandlerInterceptor) {
|
||||
try {
|
||||
((AsyncHandlerInterceptor) interceptor).postHandleAfterAsyncStarted(request, response, this.handler);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
logger.error("HandlerInterceptor.postHandleAsyncStarted(..) failed", ex);
|
||||
}
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
logger.error("HandlerInterceptor.addAsyncCallables threw exception", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger postHandleAsyncStarted callbacks on the mapped HandlerInterceptors.
|
||||
*/
|
||||
void applyPostHandleAsyncStarted(HttpServletRequest request, HttpServletResponse response)
|
||||
throws Exception {
|
||||
|
||||
if (getInterceptors() == null) {
|
||||
return;
|
||||
}
|
||||
for (int i = getInterceptors().length - 1; i >= 0; i--) {
|
||||
HandlerInterceptor interceptor = getInterceptors()[i];
|
||||
if (interceptor instanceof AsyncHandlerInterceptor) {
|
||||
try {
|
||||
((AsyncHandlerInterceptor) interceptor).postHandleAsyncStarted(request, response, this.handler);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
logger.error("HandlerInterceptor.postHandleAsyncStarted threw exception", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ public class WebRequestHandlerInterceptorAdapter implements AsyncHandlerIntercep
|
||||
return null;
|
||||
}
|
||||
|
||||
public void postHandleAsyncStarted(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||
public void postHandleAfterAsyncStarted(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||
if (this.requestInterceptor instanceof AsyncWebRequestInterceptor) {
|
||||
AsyncWebRequestInterceptor asyncInterceptor = (AsyncWebRequestInterceptor) this.requestInterceptor;
|
||||
DispatcherServletWebRequest webRequest = new DispatcherServletWebRequest(request, response);
|
||||
|
||||
@@ -59,8 +59,8 @@ public class AsyncMethodReturnValueHandler implements HandlerMethodReturnValueHa
|
||||
AsyncExecutionChain chain = AsyncExecutionChain.getForCurrentRequest(servletRequest);
|
||||
|
||||
if (Callable.class.isAssignableFrom(paramType)) {
|
||||
chain.setCallable((Callable<Object>) returnValue);
|
||||
chain.startCallableChainProcessing();
|
||||
chain.setLastCallable((Callable<Object>) returnValue);
|
||||
chain.startCallableProcessing();
|
||||
}
|
||||
else if (DeferredResult.class.isAssignableFrom(paramType)) {
|
||||
chain.startDeferredResultProcessing((DeferredResult<?>) returnValue);
|
||||
|
||||
@@ -653,14 +653,17 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
|
||||
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
|
||||
|
||||
AsyncExecutionChain chain = AsyncExecutionChain.getForCurrentRequest(request);
|
||||
chain.addDelegatingCallable(getAsyncCallable(mavContainer, modelFactory, webRequest));
|
||||
chain.setAsyncWebRequest(createAsyncWebRequest(request, response));
|
||||
chain.setTaskExecutor(this.taskExecutor);
|
||||
chain.push(getAsyncCallable(mavContainer, modelFactory, webRequest));
|
||||
|
||||
requestMappingMethod.invokeAndHandle(webRequest, mavContainer);
|
||||
|
||||
if (chain.isAsyncStarted()) {
|
||||
return null;
|
||||
try {
|
||||
requestMappingMethod.invokeAndHandle(webRequest, mavContainer);
|
||||
}
|
||||
finally {
|
||||
if (!chain.pop()) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return getModelAndView(mavContainer, modelFactory, webRequest);
|
||||
@@ -758,7 +761,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
|
||||
|
||||
return new AbstractDelegatingCallable() {
|
||||
public Object call() throws Exception {
|
||||
getNextCallable().call();
|
||||
getNext().call();
|
||||
return getModelAndView(mavContainer, modelFactory, webRequest);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -91,9 +91,6 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
|
||||
public final void invokeAndHandle(ServletWebRequest webRequest,
|
||||
ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
|
||||
|
||||
AsyncExecutionChain chain = AsyncExecutionChain.getForCurrentRequest(webRequest.getRequest());
|
||||
chain.addDelegatingCallable(geAsyncCallable(webRequest, mavContainer, providedArgs));
|
||||
|
||||
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
|
||||
|
||||
setResponseStatus(webRequest);
|
||||
@@ -111,15 +108,21 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
|
||||
|
||||
mavContainer.setRequestHandled(false);
|
||||
|
||||
AsyncExecutionChain chain = AsyncExecutionChain.getForCurrentRequest(webRequest.getRequest());
|
||||
chain.push(geAsyncCallable(webRequest, mavContainer, providedArgs));
|
||||
|
||||
try {
|
||||
this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
|
||||
|
||||
} catch (Exception ex) {
|
||||
}
|
||||
catch (Exception ex) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
finally {
|
||||
chain.pop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -131,7 +134,7 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
|
||||
return new AbstractDelegatingCallable() {
|
||||
public Object call() throws Exception {
|
||||
mavContainer.setRequestHandled(false);
|
||||
new CallableHandlerMethod(getNextCallable()).invokeAndHandle(webRequest, mavContainer, providedArgs);
|
||||
new CallableHandlerMethod(getNext()).invokeAndHandle(webRequest, mavContainer, providedArgs);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user