Skip to content

Commit d701464

Browse files
committed
Add onTimeout/onCompletion callbacks to DeferredResult
Issue: SPR-9914
1 parent 3a09644 commit d701464

File tree

21 files changed

+303
-152
lines changed

21 files changed

+303
-152
lines changed

spring-orm/src/test/java/org/springframework/orm/hibernate3/support/OpenSessionInViewTests.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ public void testOpenSessionInViewInterceptorAsyncScenario() throws Exception {
178178

179179
AsyncWebRequest asyncWebRequest = createStrictMock(AsyncWebRequest.class);
180180
asyncWebRequest.addCompletionHandler((Runnable) anyObject());
181-
asyncWebRequest.setTimeoutHandler((Runnable) anyObject());
181+
asyncWebRequest.addTimeoutHandler((Runnable) anyObject());
182182
asyncWebRequest.addCompletionHandler((Runnable) anyObject());
183183
asyncWebRequest.startAsync();
184184
replay(asyncWebRequest);
@@ -494,7 +494,7 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo
494494

495495
AsyncWebRequest asyncWebRequest = createMock(AsyncWebRequest.class);
496496
asyncWebRequest.addCompletionHandler((Runnable) anyObject());
497-
asyncWebRequest.setTimeoutHandler(EasyMock.<Runnable>anyObject());
497+
asyncWebRequest.addTimeoutHandler(EasyMock.<Runnable>anyObject());
498498
asyncWebRequest.addCompletionHandler((Runnable) anyObject());
499499
asyncWebRequest.startAsync();
500500
expect(asyncWebRequest.isAsyncStarted()).andReturn(true).anyTimes();

spring-orm/src/test/java/org/springframework/orm/jpa/support/OpenEntityManagerInViewTests.java

+3-4
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import static org.easymock.EasyMock.createMock;
2121
import static org.easymock.EasyMock.createStrictMock;
2222
import static org.easymock.EasyMock.expect;
23-
import static org.easymock.EasyMock.expectLastCall;
2423
import static org.easymock.EasyMock.replay;
2524
import static org.easymock.EasyMock.reset;
2625
import static org.easymock.EasyMock.verify;
@@ -49,8 +48,8 @@
4948
import org.springframework.web.context.WebApplicationContext;
5049
import org.springframework.web.context.request.ServletWebRequest;
5150
import org.springframework.web.context.request.async.AsyncWebRequest;
52-
import org.springframework.web.context.request.async.WebAsyncUtils;
5351
import org.springframework.web.context.request.async.WebAsyncManager;
52+
import org.springframework.web.context.request.async.WebAsyncUtils;
5453
import org.springframework.web.context.support.StaticWebApplicationContext;
5554

5655
/**
@@ -155,7 +154,7 @@ public void testOpenEntityManagerInViewInterceptorAsyncScenario() throws Excepti
155154

156155
AsyncWebRequest asyncWebRequest = createStrictMock(AsyncWebRequest.class);
157156
asyncWebRequest.addCompletionHandler((Runnable) anyObject());
158-
asyncWebRequest.setTimeoutHandler((Runnable) anyObject());
157+
asyncWebRequest.addTimeoutHandler((Runnable) anyObject());
159158
asyncWebRequest.addCompletionHandler((Runnable) anyObject());
160159
asyncWebRequest.startAsync();
161160
replay(asyncWebRequest);
@@ -346,7 +345,7 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo
346345

347346
AsyncWebRequest asyncWebRequest = createMock(AsyncWebRequest.class);
348347
asyncWebRequest.addCompletionHandler((Runnable) anyObject());
349-
asyncWebRequest.setTimeoutHandler((Runnable) anyObject());
348+
asyncWebRequest.addTimeoutHandler((Runnable) anyObject());
350349
asyncWebRequest.addCompletionHandler((Runnable) anyObject());
351350
asyncWebRequest.startAsync();
352351
expect(asyncWebRequest.isAsyncStarted()).andReturn(true).anyTimes();

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
@SuppressWarnings("serial")
4949
final class TestDispatcherServlet extends DispatcherServlet {
5050

51-
private static final String KEY = TestDispatcherServlet.class.getName() + "-interceptor";
51+
private static final String KEY = TestDispatcherServlet.class.getName() + ".interceptor";
5252

5353
/**
5454
* Create a new instance with the given web application context.

spring-test-mvc/src/main/java/org/springframework/test/web/servlet/result/RequestResultMatchers.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
import org.springframework.mock.web.MockHttpServletRequest;
2929
import org.springframework.test.web.servlet.MvcResult;
3030
import org.springframework.test.web.servlet.ResultMatcher;
31-
import org.springframework.web.context.request.async.AsyncTask;
31+
import org.springframework.web.context.request.async.MvcAsyncTask;
3232
import org.springframework.web.context.request.async.DeferredResult;
3333

3434
/**
@@ -97,7 +97,7 @@ public void match(MvcResult result) {
9797
/**
9898
* Assert the result from asynchronous processing.
9999
* This method can be used when a controller method returns {@link Callable}
100-
* or {@link AsyncTask}. The value matched is the value returned from the
100+
* or {@link MvcAsyncTask}. The value matched is the value returned from the
101101
* {@code Callable} or the exception raised.
102102
*/
103103
public <T> ResultMatcher asyncResult(Object expectedResult) {

spring-web/src/main/java/org/springframework/web/context/request/async/AsyncWebRequest.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,12 @@ public interface AsyncWebRequest extends NativeWebRequest {
3737
void setTimeout(Long timeout);
3838

3939
/**
40-
* Set the handler to use when concurrent handling has timed out.
40+
* Add a handler to invoke when concurrent handling has timed out.
4141
*/
42-
void setTimeoutHandler(Runnable runnable);
42+
void addTimeoutHandler(Runnable runnable);
4343

4444
/**
45-
* Add a Runnable to be invoked when request processing completes.
45+
* Add a handle to invoke when request processing completes.
4646
*/
4747
void addCompletionHandler(Runnable runnable);
4848

spring-web/src/main/java/org/springframework/web/context/request/async/CallableInterceptorChain.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ class CallableInterceptorChain {
3939
private int preProcessIndex = -1;
4040

4141

42-
public CallableInterceptorChain(Collection<CallableProcessingInterceptor> interceptors) {
43-
this.interceptors = new ArrayList<CallableProcessingInterceptor>(interceptors);
42+
public CallableInterceptorChain(List<CallableProcessingInterceptor> interceptors) {
43+
this.interceptors = interceptors;
4444
}
4545

4646
public void applyPreProcess(NativeWebRequest request, Callable<?> task) throws Exception {

spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResult.java

+65-28
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.apache.commons.logging.Log;
2121
import org.apache.commons.logging.LogFactory;
2222
import org.springframework.util.Assert;
23+
import org.springframework.web.context.request.NativeWebRequest;
2324

2425
/**
2526
* {@code DeferredResult} provides an alternative to using a {@link Callable}
@@ -41,6 +42,10 @@ public final class DeferredResult<T> {
4142

4243
private final Object timeoutResult;
4344

45+
private Runnable timeoutCallback;
46+
47+
private Runnable completionCallback;
48+
4449
private DeferredResultHandler resultHandler;
4550

4651
private Object result = RESULT_NONE;
@@ -56,15 +61,16 @@ public DeferredResult() {
5661
}
5762

5863
/**
59-
* Create a DeferredResult with a timeout.
64+
* Create a DeferredResult with a timeout value.
6065
* @param timeout timeout value in milliseconds
6166
*/
6267
public DeferredResult(long timeout) {
6368
this(timeout, RESULT_NONE);
6469
}
6570

6671
/**
67-
* Create a DeferredResult with a timeout and a default result to use on timeout.
72+
* Create a DeferredResult with a timeout value and a default result to use
73+
* in case of timeout.
6874
* @param timeout timeout value in milliseconds; ignored if {@code null}
6975
* @param timeoutResult the result to use
7076
*/
@@ -73,13 +79,48 @@ public DeferredResult(Long timeout, Object timeoutResult) {
7379
this.timeout = timeout;
7480
}
7581

82+
/**
83+
* Return {@code true} if this DeferredResult is no longer usable either
84+
* because it was previously set or because the underlying request expired.
85+
* <p>
86+
* The result may have been set with a call to {@link #setResult(Object)},
87+
* or {@link #setErrorResult(Object)}, or as a result of a timeout, if a
88+
* timeout result was provided to the constructor. The request may also
89+
* expire due to a timeout or network error.
90+
*/
91+
public boolean isSetOrExpired() {
92+
return ((this.result != RESULT_NONE) || this.expired);
93+
}
94+
7695
/**
7796
* Return the configured timeout value in milliseconds.
7897
*/
79-
public Long getTimeoutMilliseconds() {
98+
Long getTimeoutValue() {
8099
return this.timeout;
81100
}
82101

102+
/**
103+
* Register code to invoke when the async request times out. This method is
104+
* called from a container thread when an async request times out before the
105+
* {@code DeferredResult} has been set. It may invoke
106+
* {@link DeferredResult#setResult(Object) setResult} or
107+
* {@link DeferredResult#setErrorResult(Object) setErrorResult} to resume
108+
* processing.
109+
*/
110+
public void onTimeout(Runnable callback) {
111+
this.timeoutCallback = callback;
112+
}
113+
114+
/**
115+
* Register code to invoke when the async request completes. This method is
116+
* called from a container thread when an async request completed for any
117+
* reason including timeout and network error. This method is useful for
118+
* detecting that a {@code DeferredResult} instance is no longer usable.
119+
*/
120+
public void onCompletion(Runnable callback) {
121+
this.completionCallback = callback;
122+
}
123+
83124
/**
84125
* Provide a handler to use to handle the result value.
85126
* @param resultHandler the handler
@@ -138,33 +179,29 @@ public boolean setErrorResult(Object result) {
138179
return setResultInternal(result);
139180
}
140181

141-
/**
142-
* Return {@code true} if this DeferredResult is no longer usable either
143-
* because it was previously set or because the underlying request expired.
144-
* <p>
145-
* The result may have been set with a call to {@link #setResult(Object)},
146-
* or {@link #setErrorResult(Object)}, or as a result of a timeout, if a
147-
* timeout result was provided to the constructor. The request may also
148-
* expire due to a timeout or network error.
149-
*/
150-
public boolean isSetOrExpired() {
151-
return ((this.result != RESULT_NONE) || this.expired);
152-
}
182+
DeferredResultProcessingInterceptor getInterceptor() {
183+
return new DeferredResultProcessingInterceptorAdapter() {
153184

154-
/**
155-
* Mark this instance expired so it may no longer be used.
156-
* @return the previous value of the expiration flag
157-
*/
158-
boolean expire() {
159-
synchronized (this) {
160-
boolean previous = this.expired;
161-
this.expired = true;
162-
return previous;
163-
}
164-
}
185+
@Override
186+
public <S> void afterTimeout(NativeWebRequest request, DeferredResult<S> deferredResult) {
187+
if (timeoutCallback != null) {
188+
timeoutCallback.run();
189+
}
190+
if (DeferredResult.this.timeoutResult != RESULT_NONE) {
191+
setResultInternal(timeoutResult);
192+
}
193+
}
165194

166-
boolean applyTimeoutResult() {
167-
return (this.timeoutResult != RESULT_NONE) ? setResultInternal(this.timeoutResult) : false;
195+
@Override
196+
public <S> void afterCompletion(NativeWebRequest request, DeferredResult<S> deferredResult) {
197+
synchronized (this) {
198+
expired = true;
199+
}
200+
if (completionCallback != null) {
201+
completionCallback.run();
202+
}
203+
}
204+
};
168205
}
169206

170207

spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResultInterceptorChain.java

+2-4
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
*/
1616
package org.springframework.web.context.request.async;
1717

18-
import java.util.ArrayList;
19-
import java.util.Collection;
2018
import java.util.List;
2119

2220
import org.apache.commons.logging.Log;
@@ -38,8 +36,8 @@ class DeferredResultInterceptorChain {
3836
private int preProcessingIndex = -1;
3937

4038

41-
public DeferredResultInterceptorChain(Collection<DeferredResultProcessingInterceptor> interceptors) {
42-
this.interceptors = new ArrayList<DeferredResultProcessingInterceptor>(interceptors);
39+
public DeferredResultInterceptorChain(List<DeferredResultProcessingInterceptor> interceptors) {
40+
this.interceptors = interceptors;
4341
}
4442

4543
public void applyPreProcess(NativeWebRequest request, DeferredResult<?> deferredResult) throws Exception {

spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResultProcessingInterceptor.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public interface DeferredResultProcessingInterceptor {
7575
* Invoked from a container thread when an async request times out before
7676
* the {@code DeferredResult} has been set. Implementations may invoke
7777
* {@link DeferredResult#setResult(Object) setResult} or
78-
* {@link DeferredResult#setErrorResult(Object) to resume processing.
78+
* {@link DeferredResult#setErrorResult(Object) setErrorResult} to resume processing.
7979
*
8080
* @param request the current request
8181
* @param deferredResult the DeferredResult for the current request; if the

0 commit comments

Comments
 (0)