Add timeout async request handling to OSIV components

This change adds async web request timeout handling to OSIV filters
and interceptors to ensure the session or entity manager is released.

Issue: SPR-10874
This commit is contained in:
Rossen Stoyanchev
2013-10-30 22:50:01 -04:00
parent b69ebe70a6
commit 5d8fac86d7
12 changed files with 572 additions and 271 deletions

View File

@@ -18,13 +18,11 @@ package org.springframework.orm.hibernate3.support;
import java.io.IOException;
import java.sql.Connection;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.*;
import javax.transaction.TransactionManager;
import org.hibernate.FlushMode;
@@ -35,11 +33,7 @@ import org.hibernate.engine.SessionFactoryImplementor;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.mock.web.test.MockFilterConfig;
import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.mock.web.test.MockHttpServletResponse;
import org.springframework.mock.web.test.MockServletContext;
import org.springframework.mock.web.test.PassThroughFilterChain;
import org.springframework.mock.web.test.*;
import org.springframework.orm.hibernate3.HibernateAccessor;
import org.springframework.orm.hibernate3.HibernateTransactionManager;
import org.springframework.orm.hibernate3.SessionFactoryUtils;
@@ -50,9 +44,11 @@ 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.AsyncWebRequest;
import org.springframework.web.context.request.async.StandardServletAsyncWebRequest;
import org.springframework.web.context.request.async.WebAsyncManager;
import org.springframework.web.context.request.async.WebAsyncUtils;
import org.springframework.web.context.support.StaticWebApplicationContext;
import org.springframework.web.util.NestedServletException;
import static org.junit.Assert.*;
import static org.mockito.BDDMockito.*;
@@ -79,6 +75,7 @@ public class OpenSessionInViewTests {
public void setup() {
this.sc = new MockServletContext();
this.request = new MockHttpServletRequest(sc);
this.request.setAsyncSupported(true);
this.response = new MockHttpServletResponse();
this.webRequest = new ServletWebRequest(this.request);
}
@@ -142,12 +139,10 @@ public class OpenSessionInViewTests {
interceptor.preHandle(this.webRequest);
assertTrue(TransactionSynchronizationManager.hasResource(sf));
AsyncWebRequest asyncWebRequest = mock(AsyncWebRequest.class);
AsyncWebRequest asyncWebRequest = new StandardServletAsyncWebRequest(this.request, this.response);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(this.request);
asyncManager.setTaskExecutor(new SyncTaskExecutor());
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.startCallableProcessing(new Callable<String>() {
@Override
public String call() throws Exception {
@@ -166,13 +161,58 @@ public class OpenSessionInViewTests {
interceptor.postHandle(this.webRequest, null);
assertTrue(TransactionSynchronizationManager.hasResource(sf));
verify(session, never()).close();
interceptor.afterCompletion(this.webRequest, null);
assertFalse(TransactionSynchronizationManager.hasResource(sf));
verify(session).setFlushMode(FlushMode.MANUAL);
verify(asyncWebRequest, times(2)).addCompletionHandler(any(Runnable.class));
verify(asyncWebRequest).addTimeoutHandler(any(Runnable.class));
verify(asyncWebRequest).startAsync();
verify(session).close();
}
@Test
public void testOpenSessionInViewInterceptorAsyncTimeoutScenario() throws Exception {
// Initial request thread
final SessionFactory sf = mock(SessionFactory.class);
Session session = mock(Session.class);
OpenSessionInViewInterceptor interceptor = new OpenSessionInViewInterceptor();
interceptor.setSessionFactory(sf);
given(sf.openSession()).willReturn(session);
given(session.getSessionFactory()).willReturn(sf);
interceptor.preHandle(this.webRequest);
assertTrue(TransactionSynchronizationManager.hasResource(sf));
AsyncWebRequest asyncWebRequest = new StandardServletAsyncWebRequest(this.request, this.response);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(this.request);
asyncManager.setTaskExecutor(new SyncTaskExecutor());
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.startCallableProcessing(new Callable<String>() {
@Override
public String call() throws Exception {
return "anything";
}
});
interceptor.afterConcurrentHandlingStarted(this.webRequest);
assertFalse(TransactionSynchronizationManager.hasResource(sf));
verify(session, never()).close();
// Async request timeout
MockAsyncContext asyncContext = (MockAsyncContext) this.request.getAsyncContext();
for (AsyncListener listener : asyncContext.getListeners()) {
listener.onTimeout(new AsyncEvent(asyncContext));
}
for (AsyncListener listener : asyncContext.getListeners()) {
listener.onComplete(new AsyncEvent(asyncContext));
}
verify(session).close();
}
@Test
@@ -376,9 +416,7 @@ public class OpenSessionInViewTests {
}
};
AsyncWebRequest asyncWebRequest = mock(AsyncWebRequest.class);
given(asyncWebRequest.isAsyncStarted()).willReturn(true);
AsyncWebRequest asyncWebRequest = new StandardServletAsyncWebRequest(this.request, this.response);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(this.request);
asyncManager.setTaskExecutor(new SyncTaskExecutor());
asyncManager.setAsyncWebRequest(asyncWebRequest);
@@ -393,19 +431,89 @@ public class OpenSessionInViewTests {
filter.doFilter(this.request, this.response, filterChain);
assertFalse(TransactionSynchronizationManager.hasResource(sf));
assertEquals(1, count.get());
verify(session, never()).close();
// Async dispatch after concurrent handling produces result ...
this.request.setAsyncStarted(false);
assertFalse(TransactionSynchronizationManager.hasResource(sf));
filter.doFilter(this.request, this.response, filterChain);
assertFalse(TransactionSynchronizationManager.hasResource(sf));
assertEquals(2, count.get());
verify(session).setFlushMode(FlushMode.MANUAL);
verify(asyncWebRequest, times(2)).addCompletionHandler(any(Runnable.class));
verify(asyncWebRequest).addTimeoutHandler(any(Runnable.class));
verify(asyncWebRequest).startAsync();
verify(session).close();
wac.close();
}
@Test
public void testOpenSessionInViewFilterAsyncTimeoutScenario() throws Exception {
final SessionFactory sf = mock(SessionFactory.class);
Session session = mock(Session.class);
// Initial request during which concurrent handling starts..
given(sf.openSession()).willReturn(session);
given(session.getSessionFactory()).willReturn(sf);
StaticWebApplicationContext wac = new StaticWebApplicationContext();
wac.setServletContext(sc);
wac.getDefaultListableBeanFactory().registerSingleton("sessionFactory", sf);
wac.refresh();
sc.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, wac);
MockFilterConfig filterConfig = new MockFilterConfig(wac.getServletContext(), "filter");
final OpenSessionInViewFilter filter = new OpenSessionInViewFilter();
filter.init(filterConfig);
final AtomicInteger count = new AtomicInteger(0);
final AsyncWebRequest asyncWebRequest = new StandardServletAsyncWebRequest(this.request, this.response);
final MockHttpServletRequest request = this.request;
final FilterChain filterChain = new FilterChain() {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse)
throws NestedServletException {
assertTrue(TransactionSynchronizationManager.hasResource(sf));
count.incrementAndGet();
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(new SyncTaskExecutor());
asyncManager.setAsyncWebRequest(asyncWebRequest);
try {
asyncManager.startCallableProcessing(new Callable<String>() {
@Override
public String call() throws Exception {
return "anything";
}
});
}
catch (Exception e) {
throw new NestedServletException("", e);
}
}
};
assertFalse(TransactionSynchronizationManager.hasResource(sf));
filter.doFilter(this.request, this.response, filterChain);
assertFalse(TransactionSynchronizationManager.hasResource(sf));
assertEquals(1, count.get());
verify(session, never()).close();
// Async request timeout ...
MockAsyncContext asyncContext = (MockAsyncContext) this.request.getAsyncContext();
for (AsyncListener listener : asyncContext.getListeners()) {
listener.onTimeout(new AsyncEvent(asyncContext));
}
for (AsyncListener listener : asyncContext.getListeners()) {
listener.onComplete(new AsyncEvent(asyncContext));
}
verify(session).close();
wac.close();
}

View File

@@ -21,25 +21,19 @@ import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicInteger;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.*;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.mock.web.test.MockFilterConfig;
import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.mock.web.test.MockHttpServletResponse;
import org.springframework.mock.web.test.MockServletContext;
import org.springframework.mock.web.test.PassThroughFilterChain;
import org.springframework.mock.web.test.*;
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.AsyncWebRequest;
import org.springframework.web.context.request.async.StandardServletAsyncWebRequest;
import org.springframework.web.context.request.async.WebAsyncManager;
import org.springframework.web.context.request.async.WebAsyncUtils;
import org.springframework.web.context.support.StaticWebApplicationContext;
@@ -59,6 +53,12 @@ public class OpenEntityManagerInViewTests {
private EntityManagerFactory factory;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
private ServletWebRequest webRequest;
@Before
public void setUp() throws Exception {
@@ -66,6 +66,11 @@ public class OpenEntityManagerInViewTests {
manager = mock(EntityManager.class);
given(factory.createEntityManager()).willReturn(manager);
this.request = new MockHttpServletRequest();
this.request.setAsyncSupported(true);
this.response = new MockHttpServletResponse();
this.webRequest = new ServletWebRequest(this.request);
}
@After
@@ -79,13 +84,13 @@ public class OpenEntityManagerInViewTests {
@Test
public void testOpenEntityManagerInViewInterceptor() throws Exception {
OpenEntityManagerInViewInterceptor interceptor = new OpenEntityManagerInViewInterceptor();
interceptor.setEntityManagerFactory(factory);
interceptor.setEntityManagerFactory(this.factory);
MockServletContext sc = new MockServletContext();
MockHttpServletRequest request = new MockHttpServletRequest(sc);
interceptor.preHandle(new ServletWebRequest(request));
assertTrue(TransactionSynchronizationManager.hasResource(factory));
assertTrue(TransactionSynchronizationManager.hasResource(this.factory));
// check that further invocations simply participate
interceptor.preHandle(new ServletWebRequest(request));
@@ -120,16 +125,13 @@ public class OpenEntityManagerInViewTests {
OpenEntityManagerInViewInterceptor interceptor = new OpenEntityManagerInViewInterceptor();
interceptor.setEntityManagerFactory(factory);
MockServletContext sc = new MockServletContext();
MockHttpServletRequest request = new MockHttpServletRequest(sc);
ServletWebRequest webRequest = new ServletWebRequest(request);
given(factory.createEntityManager()).willReturn(this.manager);
interceptor.preHandle(webRequest);
interceptor.preHandle(this.webRequest);
assertTrue(TransactionSynchronizationManager.hasResource(factory));
AsyncWebRequest asyncWebRequest = mock(AsyncWebRequest.class);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(webRequest);
AsyncWebRequest asyncWebRequest = new StandardServletAsyncWebRequest(this.request, this.response);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(this.webRequest);
asyncManager.setTaskExecutor(new SyncTaskExecutor());
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.startCallableProcessing(new Callable<String>() {
@@ -139,17 +141,12 @@ public class OpenEntityManagerInViewTests {
}
});
verify(asyncWebRequest, times(2)).addCompletionHandler(any(Runnable.class));
verify(asyncWebRequest).addTimeoutHandler(any(Runnable.class));
verify(asyncWebRequest, times(2)).addCompletionHandler(any(Runnable.class));
verify(asyncWebRequest).startAsync();
interceptor.afterConcurrentHandlingStarted(webRequest);
interceptor.afterConcurrentHandlingStarted(this.webRequest);
assertFalse(TransactionSynchronizationManager.hasResource(factory));
// Async dispatch thread
interceptor.preHandle(webRequest);
interceptor.preHandle(this.webRequest);
assertTrue(TransactionSynchronizationManager.hasResource(factory));
asyncManager.clearConcurrentResult();
@@ -168,15 +165,57 @@ public class OpenEntityManagerInViewTests {
interceptor.postHandle(new ServletWebRequest(request), null);
interceptor.afterCompletion(new ServletWebRequest(request), null);
interceptor.postHandle(webRequest, null);
interceptor.postHandle(this.webRequest, null);
assertTrue(TransactionSynchronizationManager.hasResource(factory));
given(manager.isOpen()).willReturn(true);
given(this.manager.isOpen()).willReturn(true);
interceptor.afterCompletion(webRequest, null);
interceptor.afterCompletion(this.webRequest, null);
assertFalse(TransactionSynchronizationManager.hasResource(factory));
verify(manager).close();
verify(this.manager).close();
}
@Test
public void testOpenEntityManagerInViewInterceptorAsyncTimeoutScenario() throws Exception {
// Initial request thread
OpenEntityManagerInViewInterceptor interceptor = new OpenEntityManagerInViewInterceptor();
interceptor.setEntityManagerFactory(factory);
given(this.factory.createEntityManager()).willReturn(this.manager);
interceptor.preHandle(this.webRequest);
assertTrue(TransactionSynchronizationManager.hasResource(this.factory));
AsyncWebRequest asyncWebRequest = new StandardServletAsyncWebRequest(this.request, this.response);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(this.request);
asyncManager.setTaskExecutor(new SyncTaskExecutor());
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.startCallableProcessing(new Callable<String>() {
@Override
public String call() throws Exception {
return "anything";
}
});
interceptor.afterConcurrentHandlingStarted(this.webRequest);
assertFalse(TransactionSynchronizationManager.hasResource(this.factory));
// Async request timeout
given(this.manager.isOpen()).willReturn(true);
MockAsyncContext asyncContext = (MockAsyncContext) this.request.getAsyncContext();
for (AsyncListener listener : asyncContext.getListeners()) {
listener.onTimeout(new AsyncEvent(asyncContext));
}
for (AsyncListener listener : asyncContext.getListeners()) {
listener.onComplete(new AsyncEvent(asyncContext));
}
verify(this.manager).close();
}
@Test
@@ -257,8 +296,6 @@ public class OpenEntityManagerInViewTests {
wac.getDefaultListableBeanFactory().registerSingleton("myEntityManagerFactory", factory2);
wac.refresh();
sc.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, wac);
MockHttpServletRequest request = new MockHttpServletRequest(sc);
MockHttpServletResponse response = new MockHttpServletResponse();
MockFilterConfig filterConfig = new MockFilterConfig(wac.getServletContext(), "filter");
MockFilterConfig filterConfig2 = new MockFilterConfig(wac.getServletContext(), "filter2");
@@ -297,7 +334,7 @@ public class OpenEntityManagerInViewTests {
AsyncWebRequest asyncWebRequest = mock(AsyncWebRequest.class);
given(asyncWebRequest.isAsyncStarted()).willReturn(true);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(this.request);
asyncManager.setTaskExecutor(new SyncTaskExecutor());
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.startCallableProcessing(new Callable<String>() {
@@ -309,13 +346,12 @@ public class OpenEntityManagerInViewTests {
assertFalse(TransactionSynchronizationManager.hasResource(factory));
assertFalse(TransactionSynchronizationManager.hasResource(factory2));
filter2.doFilter(request, response, filterChain3);
filter2.doFilter(this.request, this.response, filterChain3);
assertFalse(TransactionSynchronizationManager.hasResource(factory));
assertFalse(TransactionSynchronizationManager.hasResource(factory2));
assertEquals(1, count.get());
assertEquals(1, count2.get());
assertNotNull(request.getAttribute("invoked"));
verify(asyncWebRequest, times(2)).addCompletionHandler(any(Runnable.class));
verify(asyncWebRequest).addTimeoutHandler(any(Runnable.class));
verify(asyncWebRequest, times(2)).addCompletionHandler(any(Runnable.class));
@@ -328,13 +364,13 @@ public class OpenEntityManagerInViewTests {
assertFalse(TransactionSynchronizationManager.hasResource(factory));
assertFalse(TransactionSynchronizationManager.hasResource(factory2));
filter.doFilter(request, response, filterChain3);
filter.doFilter(this.request, this.response, filterChain3);
assertFalse(TransactionSynchronizationManager.hasResource(factory));
assertFalse(TransactionSynchronizationManager.hasResource(factory2));
assertEquals(2, count.get());
assertEquals(2, count2.get());
verify(manager).close();
verify(this.manager).close();
verify(manager2).close();
wac.close();