Fix concurrency issue in TestDispatcherServlet
This change fixes a timing issue in tests using Spring MVC Tests where assertions on an async result may not wait long enough. The fix involves the use of a new callback in MockAsyncContext that allows tests to detect when an async dispatch has been invoked. Issue: SPR-10838
This commit is contained in:
parent
ce3e55743f
commit
119e793994
|
@ -28,7 +28,9 @@ import javax.servlet.ServletResponse;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.web.util.WebUtils;
|
import org.springframework.web.util.WebUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -49,6 +51,8 @@ public class MockAsyncContext implements AsyncContext {
|
||||||
|
|
||||||
private long timeout = 10 * 1000L; // 10 seconds is Tomcat's default
|
private long timeout = 10 * 1000L; // 10 seconds is Tomcat's default
|
||||||
|
|
||||||
|
private final List<Runnable> dispatchHandlers = new ArrayList<Runnable>();
|
||||||
|
|
||||||
|
|
||||||
public MockAsyncContext(ServletRequest request, ServletResponse response) {
|
public MockAsyncContext(ServletRequest request, ServletResponse response) {
|
||||||
this.request = (HttpServletRequest) request;
|
this.request = (HttpServletRequest) request;
|
||||||
|
@ -56,6 +60,11 @@ public class MockAsyncContext implements AsyncContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void addDispatchHandler(Runnable handler) {
|
||||||
|
Assert.notNull(handler);
|
||||||
|
this.dispatchHandlers.add(handler);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ServletRequest getRequest() {
|
public ServletRequest getRequest() {
|
||||||
return this.request;
|
return this.request;
|
||||||
|
@ -84,6 +93,9 @@ public class MockAsyncContext implements AsyncContext {
|
||||||
@Override
|
@Override
|
||||||
public void dispatch(ServletContext context, String path) {
|
public void dispatch(ServletContext context, String path) {
|
||||||
this.dispatchedPath = path;
|
this.dispatchedPath = path;
|
||||||
|
for (Runnable r : this.dispatchHandlers) {
|
||||||
|
r.run();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDispatchedPath() {
|
public String getDispatchedPath() {
|
||||||
|
|
|
@ -25,13 +25,10 @@ import javax.servlet.ServletRequest;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.springframework.mock.web.MockAsyncContext;
|
||||||
import org.springframework.web.context.WebApplicationContext;
|
import org.springframework.web.context.WebApplicationContext;
|
||||||
import org.springframework.web.context.request.NativeWebRequest;
|
import org.springframework.web.context.request.NativeWebRequest;
|
||||||
import org.springframework.web.context.request.async.CallableProcessingInterceptorAdapter;
|
import org.springframework.web.context.request.async.*;
|
||||||
import org.springframework.web.context.request.async.DeferredResult;
|
|
||||||
import org.springframework.web.context.request.async.DeferredResultProcessingInterceptorAdapter;
|
|
||||||
import org.springframework.web.context.request.async.WebAsyncManager;
|
|
||||||
import org.springframework.web.context.request.async.WebAsyncUtils;
|
|
||||||
import org.springframework.web.servlet.DispatcherServlet;
|
import org.springframework.web.servlet.DispatcherServlet;
|
||||||
import org.springframework.web.servlet.HandlerExecutionChain;
|
import org.springframework.web.servlet.HandlerExecutionChain;
|
||||||
import org.springframework.web.servlet.ModelAndView;
|
import org.springframework.web.servlet.ModelAndView;
|
||||||
|
@ -50,6 +47,7 @@ final class TestDispatcherServlet extends DispatcherServlet {
|
||||||
|
|
||||||
private static final String KEY = TestDispatcherServlet.class.getName() + ".interceptor";
|
private static final String KEY = TestDispatcherServlet.class.getName() + ".interceptor";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new instance with the given web application context.
|
* Create a new instance with the given web application context.
|
||||||
*/
|
*/
|
||||||
|
@ -57,37 +55,44 @@ final class TestDispatcherServlet extends DispatcherServlet {
|
||||||
super(webApplicationContext);
|
super(webApplicationContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
|
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
|
||||||
|
|
||||||
CountDownLatch latch = registerAsyncInterceptors(request);
|
registerAsyncResultInterceptors(request);
|
||||||
getMvcResult(request).setAsyncResultLatch(latch);
|
|
||||||
|
|
||||||
super.service(request, response);
|
super.service(request, response);
|
||||||
|
|
||||||
|
if (request.isAsyncStarted()) {
|
||||||
|
addAsyncResultLatch(request);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private CountDownLatch registerAsyncInterceptors(final HttpServletRequest servletRequest) {
|
private void registerAsyncResultInterceptors(final HttpServletRequest request) {
|
||||||
|
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
|
||||||
final CountDownLatch asyncResultLatch = new CountDownLatch(1);
|
|
||||||
|
|
||||||
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(servletRequest);
|
|
||||||
|
|
||||||
asyncManager.registerCallableInterceptor(KEY, new CallableProcessingInterceptorAdapter() {
|
asyncManager.registerCallableInterceptor(KEY, new CallableProcessingInterceptorAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public <T> void postProcess(NativeWebRequest request, Callable<T> task, Object value) throws Exception {
|
public <T> void postProcess(NativeWebRequest r, Callable<T> task, Object value) throws Exception {
|
||||||
getMvcResult(servletRequest).setAsyncResult(value);
|
getMvcResult(request).setAsyncResult(value);
|
||||||
asyncResultLatch.countDown();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
asyncManager.registerDeferredResultInterceptor(KEY, new DeferredResultProcessingInterceptorAdapter() {
|
asyncManager.registerDeferredResultInterceptor(KEY, new DeferredResultProcessingInterceptorAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public <T> void postProcess(NativeWebRequest request, DeferredResult<T> result, Object value) throws Exception {
|
public <T> void postProcess(NativeWebRequest r, DeferredResult<T> result, Object value) throws Exception {
|
||||||
getMvcResult(servletRequest).setAsyncResult(value);
|
getMvcResult(request).setAsyncResult(value);
|
||||||
asyncResultLatch.countDown();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return asyncResultLatch;
|
private void addAsyncResultLatch(HttpServletRequest request) {
|
||||||
|
final CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
((MockAsyncContext) request.getAsyncContext()).addDispatchHandler(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
latch.countDown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
getMvcResult(request).setAsyncResultLatch(latch);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected DefaultMvcResult getMvcResult(ServletRequest request) {
|
protected DefaultMvcResult getMvcResult(ServletRequest request) {
|
||||||
|
|
Loading…
Reference in New Issue