Refactor Servlet 3 async support

As a result of the refactoring, the AsyncContext dispatch mechanism is
used much more centrally. Effectively every asynchronously processed
request involves one initial (container) thread, a second thread to
produce the handler return value asynchronously, and a third thread
as a result of a dispatch back to the container to resume processing
of the asynchronous resuilt.

Other updates include the addition of a MockAsyncContext and support
of related request method in the test packages of spring-web and
spring-webmvc. Also an upgrade of a Jetty test dependency required
to make tests pass.

Issue: SPR-9433
This commit is contained in:
Rossen Stoyanchev
2012-07-24 16:00:05 -04:00
parent 026ee846c7
commit 529e62921d
47 changed files with 1837 additions and 1741 deletions

View File

@@ -16,8 +16,10 @@
package org.springframework.orm.hibernate3.support;
import static org.easymock.EasyMock.anyObject;
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;
@@ -29,6 +31,7 @@ import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.sql.Connection;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
@@ -59,9 +62,9 @@ import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.async.AbstractDelegatingCallable;
import org.springframework.web.context.request.async.AsyncExecutionChain;
import org.springframework.web.context.request.async.AsyncWebRequest;
import org.springframework.web.context.request.async.AsyncWebUtils;
import org.springframework.web.context.request.async.WebAsyncManager;
import org.springframework.web.context.support.StaticWebApplicationContext;
@@ -152,6 +155,8 @@ public class OpenSessionInViewTests {
@Test
public void testOpenSessionInViewInterceptorAsyncScenario() throws Exception {
// Initial request thread
final SessionFactory sf = createStrictMock(SessionFactory.class);
Session session = createStrictMock(Session.class);
@@ -167,39 +172,52 @@ public class OpenSessionInViewTests {
interceptor.preHandle(this.webRequest);
assertTrue(TransactionSynchronizationManager.hasResource(sf));
AbstractDelegatingCallable asyncCallable = interceptor.getAsyncCallable(this.webRequest);
assertNotNull(asyncCallable);
interceptor.postHandleAsyncStarted(this.webRequest);
assertFalse(TransactionSynchronizationManager.hasResource(sf));
verify(sf);
verify(session);
asyncCallable.setNext(new Callable<Object>() {
public Object call() {
return null;
AsyncWebRequest asyncWebRequest = createStrictMock(AsyncWebRequest.class);
asyncWebRequest.addCompletionHandler((Runnable) anyObject());
asyncWebRequest.startAsync();
replay(asyncWebRequest);
WebAsyncManager asyncManager = AsyncWebUtils.getAsyncManager(this.request);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.startCallableProcessing(new Callable<String>() {
public String call() throws Exception {
return "anything";
}
});
asyncCallable.call();
verify(asyncWebRequest);
interceptor.afterConcurrentHandlingStarted(this.webRequest);
assertFalse(TransactionSynchronizationManager.hasResource(sf));
// Async dispatch thread
interceptor.preHandle(this.webRequest);
assertTrue("Session not bound to async thread", TransactionSynchronizationManager.hasResource(sf));
verify(sf);
verify(session);
reset(sf);
reset(session);
replay(sf);
verify(session);
reset(session);
replay(session);
interceptor.postHandle(this.webRequest, null);
assertTrue(TransactionSynchronizationManager.hasResource(sf));
verify(sf);
verify(session);
reset(sf);
verify(session);
reset(session);
expect(session.close()).andReturn(null);
replay(sf);
replay(session);
@@ -438,10 +456,12 @@ public class OpenSessionInViewTests {
}
@Test
public void testOpenSessionInViewFilterWithSingleSessionAsyncScenario() throws Exception {
public void testOpenSessionInViewFilterAsyncScenario() throws Exception {
final SessionFactory sf = createStrictMock(SessionFactory.class);
Session session = createStrictMock(Session.class);
// Initial request during which concurrent handler execution starts..
expect(sf.openSession()).andReturn(session);
expect(session.getSessionFactory()).andReturn(sf);
session.setFlushMode(FlushMode.MANUAL);
@@ -456,55 +476,60 @@ public class OpenSessionInViewTests {
MockFilterConfig filterConfig = new MockFilterConfig(wac.getServletContext(), "filter");
final AtomicInteger count = new AtomicInteger(0);
final OpenSessionInViewFilter filter = new OpenSessionInViewFilter();
filter.init(filterConfig);
final FilterChain filterChain = new FilterChain() {
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse) {
assertTrue(TransactionSynchronizationManager.hasResource(sf));
servletRequest.setAttribute("invoked", Boolean.TRUE);
count.incrementAndGet();
}
};
AsyncWebRequest asyncWebRequest = createStrictMock(AsyncWebRequest.class);
asyncWebRequest.addCompletionHandler((Runnable) anyObject());
asyncWebRequest.startAsync();
expect(asyncWebRequest.isAsyncStarted()).andReturn(true);
expect(asyncWebRequest.isAsyncStarted()).andReturn(true);
expectLastCall().anyTimes();
replay(asyncWebRequest);
AsyncExecutionChain chain = AsyncExecutionChain.getForCurrentRequest(this.request);
chain.setAsyncWebRequest(asyncWebRequest);
WebAsyncManager asyncManager = AsyncWebUtils.getAsyncManager(this.request);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.startCallableProcessing(new Callable<String>() {
public String call() throws Exception {
return "anything";
}
});
assertFalse(TransactionSynchronizationManager.hasResource(sf));
filter.doFilter(this.request, this.response, filterChain);
assertFalse(TransactionSynchronizationManager.hasResource(sf));
assertNotNull(this.request.getAttribute("invoked"));
assertEquals(1, count.get());
verify(sf);
verify(session);
verify(asyncWebRequest);
chain.setTaskExecutor(new SyncTaskExecutor());
chain.setLastCallable(new Callable<Object>() {
public Object call() {
assertTrue(TransactionSynchronizationManager.hasResource(sf));
return null;
}
});
reset(asyncWebRequest);
asyncWebRequest.startAsync();
expect(asyncWebRequest.isAsyncCompleted()).andReturn(false);
asyncWebRequest.complete();
replay(asyncWebRequest);
reset(sf);
reset(session);
reset(asyncWebRequest);
// Async dispatch after concurrent handler execution results ready..
expect(session.close()).andReturn(null);
expect(asyncWebRequest.isAsyncStarted()).andReturn(false);
expectLastCall().anyTimes();
replay(sf);
replay(session);
replay(asyncWebRequest);
chain.startCallableProcessing();
assertFalse(TransactionSynchronizationManager.hasResource(sf));
filter.doFilter(this.request, this.response, filterChain);
assertFalse(TransactionSynchronizationManager.hasResource(sf));
assertEquals(2, count.get());
verify(sf);
verify(session);