Polish async request processing

This change fixes a cyclical package dependency.

The change also improves the implementation of
WebAsyncManager.hasConcurrentResult() following the resolution of
Apache issue id=53632 and the release of Apache Tomcat 7.0.30 that
contains the fix.
This commit is contained in:
Rossen Stoyanchev 2012-09-07 21:30:11 -04:00
parent 988f376752
commit 6e85dd8917
14 changed files with 120 additions and 114 deletions

View File

@ -24,8 +24,8 @@ import org.springframework.orm.hibernate3.SessionFactoryUtils;
import org.springframework.orm.hibernate3.SessionHolder;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.ui.ModelMap;
import org.springframework.web.context.request.AsyncWebRequestInterceptor;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.async.AsyncWebRequestInterceptor;
import org.springframework.web.context.request.async.AsyncWebUtils;
import org.springframework.web.context.request.async.WebAsyncManager;
import org.springframework.web.context.request.async.WebAsyncManager.WebAsyncThreadInitializer;

View File

@ -28,8 +28,8 @@ import org.springframework.orm.hibernate4.SessionFactoryUtils;
import org.springframework.orm.hibernate4.SessionHolder;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.ui.ModelMap;
import org.springframework.web.context.request.AsyncWebRequestInterceptor;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.async.AsyncWebRequestInterceptor;
import org.springframework.web.context.request.async.AsyncWebUtils;
import org.springframework.web.context.request.async.WebAsyncManager;
import org.springframework.web.context.request.async.WebAsyncManager.WebAsyncThreadInitializer;

View File

@ -26,8 +26,8 @@ import org.springframework.orm.jpa.EntityManagerFactoryUtils;
import org.springframework.orm.jpa.EntityManagerHolder;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.ui.ModelMap;
import org.springframework.web.context.request.AsyncWebRequestInterceptor;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.async.AsyncWebRequestInterceptor;
import org.springframework.web.context.request.async.AsyncWebUtils;
import org.springframework.web.context.request.async.WebAsyncManager;
import org.springframework.web.context.request.async.WebAsyncManager.WebAsyncThreadInitializer;

View File

@ -17,9 +17,9 @@
package org.springframework.orm.hibernate3.support;
import static org.easymock.EasyMock.anyObject;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.createStrictMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.reset;
import static org.easymock.EasyMock.verify;
@ -196,6 +196,10 @@ public class OpenSessionInViewTests {
// Async dispatch thread
reset(asyncWebRequest);
expect(asyncWebRequest.isDispatched()).andReturn(true);
replay(asyncWebRequest);
interceptor.preHandle(this.webRequest);
assertTrue("Session not bound to async thread", TransactionSynchronizationManager.hasResource(sf));
@ -488,11 +492,11 @@ public class OpenSessionInViewTests {
}
};
AsyncWebRequest asyncWebRequest = createStrictMock(AsyncWebRequest.class);
AsyncWebRequest asyncWebRequest = createMock(AsyncWebRequest.class);
asyncWebRequest.addCompletionHandler((Runnable) anyObject());
asyncWebRequest.startAsync();
expect(asyncWebRequest.isAsyncStarted()).andReturn(true);
expectLastCall().anyTimes();
expect(asyncWebRequest.isAsyncStarted()).andReturn(true).anyTimes();
expect(asyncWebRequest.isDispatched()).andReturn(false).anyTimes();
replay(asyncWebRequest);
WebAsyncManager asyncManager = AsyncWebUtils.getAsyncManager(this.request);
@ -520,6 +524,7 @@ public class OpenSessionInViewTests {
expect(session.close()).andReturn(null);
expect(asyncWebRequest.isAsyncStarted()).andReturn(false).anyTimes();
expect(asyncWebRequest.isDispatched()).andReturn(true).anyTimes();
replay(sf);
replay(session);

View File

@ -24,8 +24,6 @@ import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.reset;
import static org.easymock.EasyMock.verify;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import java.io.IOException;
import java.util.concurrent.Callable;
@ -161,7 +159,6 @@ public class OpenEntityManagerInViewTests extends TestCase {
WebAsyncManager asyncManager = AsyncWebUtils.getAsyncManager(webRequest);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.startCallableProcessing(new Callable<String>() {
public String call() throws Exception {
return "anything";
@ -169,14 +166,16 @@ public class OpenEntityManagerInViewTests extends TestCase {
});
verify(asyncWebRequest);
reset(asyncWebRequest);
replay(asyncWebRequest);
interceptor.afterConcurrentHandlingStarted(webRequest);
assertFalse(TransactionSynchronizationManager.hasResource(factory));
// Async dispatch thread
reset(asyncWebRequest);
expect(asyncWebRequest.isDispatched()).andReturn(true);
replay(asyncWebRequest);
reset(manager, factory);
replay(manager, factory);
@ -345,11 +344,11 @@ public class OpenEntityManagerInViewTests extends TestCase {
FilterChain filterChain3 = new PassThroughFilterChain(filter2, filterChain2);
AsyncWebRequest asyncWebRequest = createStrictMock(AsyncWebRequest.class);
AsyncWebRequest asyncWebRequest = createMock(AsyncWebRequest.class);
asyncWebRequest.addCompletionHandler((Runnable) anyObject());
asyncWebRequest.startAsync();
expect(asyncWebRequest.isAsyncStarted()).andReturn(true);
expectLastCall().anyTimes();
expect(asyncWebRequest.isAsyncStarted()).andReturn(true).anyTimes();
expect(asyncWebRequest.isDispatched()).andReturn(false).anyTimes();
replay(asyncWebRequest);
WebAsyncManager asyncManager = AsyncWebUtils.getAsyncManager(request);
@ -373,6 +372,7 @@ public class OpenEntityManagerInViewTests extends TestCase {
reset(asyncWebRequest);
expect(asyncWebRequest.isAsyncStarted()).andReturn(false).anyTimes();
expect(asyncWebRequest.isDispatched()).andReturn(true).anyTimes();
replay(asyncWebRequest);
assertFalse(TransactionSynchronizationManager.hasResource(factory));

View File

@ -0,0 +1,49 @@
/*
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.context.request;
/**
* Extends {@code WebRequestInterceptor} with a callback method invoked during
* asynchronous request handling.
*
* <p>When a handler starts asynchronous request handling, the DispatcherServlet
* exits without invoking {@code postHandle} and {@code afterCompletion}, as it
* normally does, since the results of request handling (e.g. ModelAndView) are
* not available in the current thread and handling is not yet complete.
* In such scenarios, the {@link #afterConcurrentHandlingStarted(WebRequest)}
* method is invoked instead allowing implementations to perform tasks such as
* cleaning up thread bound attributes.
*
* <p>When asynchronous handling completes, the request is dispatched to the
* container for further processing. At this stage the DispatcherServlet invokes
* {@code preHandle}, {@code postHandle} and {@code afterCompletion} as usual.
*
* @author Rossen Stoyanchev
* @since 3.2
*
* @see org.springframework.web.context.request.async.WebAsyncManager
*/
public interface AsyncWebRequestInterceptor extends WebRequestInterceptor{
/**
* Called instead of {@code postHandle} and {@code afterCompletion}, when the
* the handler started handling the request concurrently.
*
* @param request the current request
*/
void afterConcurrentHandlingStarted(WebRequest request);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2008 the original author or authors.
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -19,7 +19,6 @@ package org.springframework.web.context.request;
import org.apache.log4j.Logger;
import org.apache.log4j.NDC;
import org.springframework.ui.ModelMap;
import org.springframework.web.context.request.async.AsyncWebUtils;
/**
* Request logging interceptor that adds a request context message to the
@ -31,7 +30,7 @@ import org.springframework.web.context.request.async.AsyncWebUtils;
* @see org.apache.log4j.NDC#push(String)
* @see org.apache.log4j.NDC#pop()
*/
public class Log4jNestedDiagnosticContextInterceptor implements WebRequestInterceptor {
public class Log4jNestedDiagnosticContextInterceptor implements AsyncWebRequestInterceptor {
/** Logger available to subclasses */
protected final Logger log4jLogger = Logger.getLogger(getClass());
@ -60,11 +59,6 @@ public class Log4jNestedDiagnosticContextInterceptor implements WebRequestInterc
* Adds a message the Log4J NDC before the request is processed.
*/
public void preHandle(WebRequest request) throws Exception {
if (AsyncWebUtils.getAsyncManager(request).hasConcurrentResult()) {
return;
}
NDC.push(getNestedDiagnosticContextMessage(request));
}
@ -93,4 +87,15 @@ public class Log4jNestedDiagnosticContextInterceptor implements WebRequestInterc
}
}
/**
* Removes the log message from the Log4J NDC when the processing thread is
* exited after the start of asynchronous request handling.
*/
public void afterConcurrentHandlingStarted(WebRequest request) {
NDC.pop();
if (NDC.getDepth() == 0) {
NDC.remove();
}
}
}

View File

@ -37,7 +37,7 @@ public interface AsyncWebRequest extends NativeWebRequest {
void setTimeout(Long timeout);
/**
* Set a handler to be invoked if concurrent processing times out.
* Set the handler to use when concurrent handling has timed out.
*/
void setTimeoutHandler(Runnable runnable);

View File

@ -1,55 +0,0 @@
/*
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.context.request.async;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.WebRequestInterceptor;
/**
* Extends the WebRequestInterceptor contract for scenarios where a handler may be
* executed asynchronously. Since the handler will complete execution in another
* thread, the results are not available in the current thread, and therefore the
* DispatcherServlet exits quickly and on its way out invokes
* {@link #afterConcurrentHandlingStarted(WebRequest)} instead of
* {@code postHandle} and {@code afterCompletion}.
* When the async handler execution completes, and the request is dispatched back
* for further processing, the DispatcherServlet will invoke {@code preHandle}
* again, as well as {@code postHandle} and {@code afterCompletion}.
*
* <p>Existing implementations should consider the fact that {@code preHandle} may
* be invoked twice before {@code postHandle} and {@code afterCompletion} are
* called if they don't implement this contract. Once before the start of concurrent
* handling and a second time as part of an asynchronous dispatch after concurrent
* handling is done. This may be not important in most cases but when some work
* needs to be done after concurrent handling starts (e.g. clearing thread locals)
* then this contract can be implemented.
*
* @author Rossen Stoyanchev
* @since 3.2
*
* @see WebAsyncManager
*/
public interface AsyncWebRequestInterceptor extends WebRequestInterceptor{
/**
* Called instead of {@code postHandle} and {@code afterCompletion}, when the
* the handler started handling the request concurrently.
*
* @param request the current request
*/
void afterConcurrentHandlingStarted(WebRequest request);
}

View File

@ -22,8 +22,7 @@ import javax.servlet.http.HttpServletResponse;
import org.springframework.web.context.request.ServletWebRequest;
/**
* An implementation of {@link AsyncWebRequest} to use when no underlying support is available.
* The methods {@link #startAsync()} and {@link #dispatch()} raise {@link UnsupportedOperationException}.
* An {@code AsyncWebRequest} to use when there is no underlying async support.
*
* @author Rossen Stoyanchev
* @since 3.2
@ -54,11 +53,13 @@ public class NoSupportAsyncWebRequest extends ServletWebRequest implements Async
return false;
}
public boolean isAsyncComplete() {
// Not supported
public void startAsync() {
throw new UnsupportedOperationException("No async support in a pre-Servlet 3.0 runtime");
}
public void startAsync() {
public boolean isAsyncComplete() {
throw new UnsupportedOperationException("No async support in a pre-Servlet 3.0 runtime");
}

View File

@ -67,13 +67,19 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
/**
* {@inheritDoc}
* <p>The timeout period begins when the main processing thread has exited.
* <p>In Servlet 3 async processing, the timeout period begins after the
* container processing thread has exited.
*/
public void setTimeout(Long timeout) {
Assert.state(!isAsyncStarted(), "Cannot change the timeout with concurrent handling in progress");
this.timeout = timeout;
}
/**
* {@inheritDoc}
* <p>If not set, by default a timeout is handled by returning
* SERVICE_UNAVAILABLE (503).
*/
public void setTimeoutHandler(Runnable timeoutHandler) {
if (timeoutHandler != null) {
this.timeoutHandler = timeoutHandler;

View File

@ -52,7 +52,7 @@ import org.springframework.web.util.UrlPathHelper;
* @author Rossen Stoyanchev
* @since 3.2
*
* @see org.springframework.web.context.request.async.AsyncWebRequestInterceptor
* @see org.springframework.web.context.request.AsyncWebRequestInterceptor
* @see org.springframework.web.servlet.AsyncHandlerInterceptor
* @see org.springframework.web.filter.OncePerRequestFilter#shouldFilterAsyncDispatches
* @see org.springframework.web.filter.OncePerRequestFilter#isAsyncDispatch
@ -115,26 +115,23 @@ public final class WebAsyncManager {
}
/**
* Whether the target handler chose to handle the request asynchronously.
* A return value of "true" indicates concurrent handling is under way and the
* response will remain open. A return value of "false" will be returned again after concurrent
* handling produces a result and the request is dispatched to resume processing.
* Whether the selected handler for the current request chose to handle the
* request asynchronously. A return value of "true" indicates concurrent
* handling is under way and the response will remain open. A return value
* of "false" means concurrent handling was either not started or possibly
* that it has completed and the request was dispatched for further
* processing of the concurrent result.
*/
public boolean isConcurrentHandlingStarted() {
return ((this.asyncWebRequest != null) && (this.asyncWebRequest.isAsyncStarted()));
return ((this.asyncWebRequest != null) && this.asyncWebRequest.isAsyncStarted());
}
/**
* Whether the request was dispatched to resume processing the result of
* Whether the request has been dispatched to process the result of
* concurrent handling.
*/
public boolean hasConcurrentResult() {
// TODO:
// Add check for asyncWebRequest.isDispatched() once Apache id=53632 is fixed.
// That ensure "true" is returned in the dispatched thread only.
return this.concurrentResult != RESULT_NONE;
return ((this.concurrentResult != RESULT_NONE) && this.asyncWebRequest.isDispatched());
}
/**

View File

@ -20,23 +20,21 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Extends the HandlerInterceptor contract for scenarios where a handler may be
* executed asynchronously. Since the handler will complete execution in another
* thread, the results are not available in the current thread, and therefore the
* DispatcherServlet exits quickly and on its way out invokes
* {@link #afterConcurrentHandlingStarted(HttpServletRequest, HttpServletResponse)}
* instead of {@code postHandle} and {@code afterCompletion}.
* When the async handler execution completes, and the request is dispatched back
* for further processing, the DispatcherServlet will invoke {@code preHandle}
* again, as well as {@code postHandle} and {@code afterCompletion}.
* Extends {@code HandlerInterceptor} with a callback method invoked during
* asynchronous request handling.
*
* <p>Existing implementations should consider the fact that {@code preHandle} may
* be invoked twice before {@code postHandle} and {@code afterCompletion} are
* called if they don't implement this contract. Once before the start of concurrent
* handling and a second time as part of an asynchronous dispatch after concurrent
* handling is done. This may be not important in most cases but when some work
* needs to be done after concurrent handling starts (e.g. clearing thread locals)
* then this contract can be implemented.
* <p>When a handler starts asynchronous request handling, the DispatcherServlet
* exits without invoking {@code postHandle} and {@code afterCompletion}, as it
* normally does, since the results of request handling (e.g. ModelAndView) are
* not available in the current thread and handling is not yet complete.
* In such scenarios, the
* {@link #afterConcurrentHandlingStarted(HttpServletRequest, HttpServletResponse)}
* method is invoked instead allowing implementations to perform tasks such as
* cleaning up thread bound attributes.
*
* <p>When asynchronous handling completes, the request is dispatched to the
* container for further processing. At this stage the DispatcherServlet invokes
* {@code preHandle}, {@code postHandle} and {@code afterCompletion} as usual.
*
* @author Rossen Stoyanchev
* @since 3.2

View File

@ -20,8 +20,8 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.util.Assert;
import org.springframework.web.context.request.AsyncWebRequestInterceptor;
import org.springframework.web.context.request.WebRequestInterceptor;
import org.springframework.web.context.request.async.AsyncWebRequestInterceptor;
import org.springframework.web.servlet.AsyncHandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;