Skip to content

Commit 119e793

Browse files
committed
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
1 parent ce3e557 commit 119e793

File tree

2 files changed

+37
-20
lines changed

2 files changed

+37
-20
lines changed

spring-test/src/main/java/org/springframework/mock/web/MockAsyncContext.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@
2828
import javax.servlet.http.HttpServletRequest;
2929
import javax.servlet.http.HttpServletResponse;
3030

31+
import org.apache.commons.logging.Log;
3132
import org.springframework.beans.BeanUtils;
33+
import org.springframework.util.Assert;
3234
import org.springframework.web.util.WebUtils;
3335

3436
/**
@@ -49,13 +51,20 @@ public class MockAsyncContext implements AsyncContext {
4951

5052
private long timeout = 10 * 1000L; // 10 seconds is Tomcat's default
5153

54+
private final List<Runnable> dispatchHandlers = new ArrayList<Runnable>();
55+
5256

5357
public MockAsyncContext(ServletRequest request, ServletResponse response) {
5458
this.request = (HttpServletRequest) request;
5559
this.response = (HttpServletResponse) response;
5660
}
5761

5862

63+
public void addDispatchHandler(Runnable handler) {
64+
Assert.notNull(handler);
65+
this.dispatchHandlers.add(handler);
66+
}
67+
5968
@Override
6069
public ServletRequest getRequest() {
6170
return this.request;
@@ -84,6 +93,9 @@ public void dispatch(String path) {
8493
@Override
8594
public void dispatch(ServletContext context, String path) {
8695
this.dispatchedPath = path;
96+
for (Runnable r : this.dispatchHandlers) {
97+
r.run();
98+
}
8799
}
88100

89101
public String getDispatchedPath() {

spring-test/src/main/java/org/springframework/test/web/servlet/TestDispatcherServlet.java

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,10 @@
2525
import javax.servlet.http.HttpServletRequest;
2626
import javax.servlet.http.HttpServletResponse;
2727

28+
import org.springframework.mock.web.MockAsyncContext;
2829
import org.springframework.web.context.WebApplicationContext;
2930
import org.springframework.web.context.request.NativeWebRequest;
30-
import org.springframework.web.context.request.async.CallableProcessingInterceptorAdapter;
31-
import org.springframework.web.context.request.async.DeferredResult;
32-
import org.springframework.web.context.request.async.DeferredResultProcessingInterceptorAdapter;
33-
import org.springframework.web.context.request.async.WebAsyncManager;
34-
import org.springframework.web.context.request.async.WebAsyncUtils;
31+
import org.springframework.web.context.request.async.*;
3532
import org.springframework.web.servlet.DispatcherServlet;
3633
import org.springframework.web.servlet.HandlerExecutionChain;
3734
import org.springframework.web.servlet.ModelAndView;
@@ -50,44 +47,52 @@ final class TestDispatcherServlet extends DispatcherServlet {
5047

5148
private static final String KEY = TestDispatcherServlet.class.getName() + ".interceptor";
5249

50+
5351
/**
5452
* Create a new instance with the given web application context.
5553
*/
5654
public TestDispatcherServlet(WebApplicationContext webApplicationContext) {
5755
super(webApplicationContext);
5856
}
5957

58+
6059
@Override
6160
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
6261

63-
CountDownLatch latch = registerAsyncInterceptors(request);
64-
getMvcResult(request).setAsyncResultLatch(latch);
62+
registerAsyncResultInterceptors(request);
6563

6664
super.service(request, response);
67-
}
6865

69-
private CountDownLatch registerAsyncInterceptors(final HttpServletRequest servletRequest) {
70-
71-
final CountDownLatch asyncResultLatch = new CountDownLatch(1);
72-
73-
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(servletRequest);
66+
if (request.isAsyncStarted()) {
67+
addAsyncResultLatch(request);
68+
}
69+
}
7470

71+
private void registerAsyncResultInterceptors(final HttpServletRequest request) {
72+
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
7573
asyncManager.registerCallableInterceptor(KEY, new CallableProcessingInterceptorAdapter() {
7674
@Override
77-
public <T> void postProcess(NativeWebRequest request, Callable<T> task, Object value) throws Exception {
78-
getMvcResult(servletRequest).setAsyncResult(value);
79-
asyncResultLatch.countDown();
75+
public <T> void postProcess(NativeWebRequest r, Callable<T> task, Object value) throws Exception {
76+
getMvcResult(request).setAsyncResult(value);
8077
}
8178
});
8279
asyncManager.registerDeferredResultInterceptor(KEY, new DeferredResultProcessingInterceptorAdapter() {
8380
@Override
84-
public <T> void postProcess(NativeWebRequest request, DeferredResult<T> result, Object value) throws Exception {
85-
getMvcResult(servletRequest).setAsyncResult(value);
86-
asyncResultLatch.countDown();
81+
public <T> void postProcess(NativeWebRequest r, DeferredResult<T> result, Object value) throws Exception {
82+
getMvcResult(request).setAsyncResult(value);
8783
}
8884
});
85+
}
8986

90-
return asyncResultLatch;
87+
private void addAsyncResultLatch(HttpServletRequest request) {
88+
final CountDownLatch latch = new CountDownLatch(1);
89+
((MockAsyncContext) request.getAsyncContext()).addDispatchHandler(new Runnable() {
90+
@Override
91+
public void run() {
92+
latch.countDown();
93+
}
94+
});
95+
getMvcResult(request).setAsyncResultLatch(latch);
9196
}
9297

9398
protected DefaultMvcResult getMvcResult(ServletRequest request) {

0 commit comments

Comments
 (0)