Parameterize DeferredResult

Issue: SPR-9579
This commit is contained in:
Rossen Stoyanchev
2012-07-18 09:43:03 -04:00
parent 55bd99fa16
commit eab6e1d134
6 changed files with 85 additions and 87 deletions

View File

@@ -181,7 +181,7 @@ public final class AsyncExecutionChain {
* the threading model, i.e. whether a TaskExecutor is used.
* @see DeferredResult
*/
public void startDeferredResultProcessing(final DeferredResult deferredResult) {
public void startDeferredResultProcessing(final DeferredResult<?> deferredResult) {
Assert.notNull(deferredResult, "DeferredResult is required");
startAsync();
deferredResult.init(new DeferredResultHandler() {
@@ -193,13 +193,7 @@ public final class AsyncExecutionChain {
new AsyncExecutionChainRunnable(asyncWebRequest, buildChain()).run();
}
});
if (deferredResult.canHandleTimeout()) {
this.asyncWebRequest.setTimeoutHandler(new Runnable() {
public void run() {
deferredResult.handleTimeout();
}
});
}
this.asyncWebRequest.setTimeoutHandler(deferredResult.getTimeoutHandler());
}

View File

@@ -47,68 +47,68 @@ import org.springframework.util.Assert;
* @author Rossen Stoyanchev
* @since 3.2
*/
public final class DeferredResult {
public final class DeferredResult<V> {
private final static Object TIMEOUT_RESULT_NONE = new Object();
private Object result;
private final Object timeoutResult;
private V result;
private DeferredResultHandler resultHandler;
private final CountDownLatch readySignal = new CountDownLatch(1);
private final V timeoutValue;
private final ReentrantLock timeoutLock = new ReentrantLock();
private final boolean timeoutValueSet;
private boolean timeoutValueUsed;
private final CountDownLatch initializationLatch = new CountDownLatch(1);
private final ReentrantLock setLock = new ReentrantLock();
/**
* Create a new instance.
*/
public DeferredResult() {
this(TIMEOUT_RESULT_NONE);
this.timeoutValue = null;
this.timeoutValueSet = false;
}
/**
* Create a new instance and also provide a default result to use if a
* timeout occurs before {@link #set(Object)} is called.
* Create a new instance also providing a default value to set if a timeout
* occurs before {@link #set(Object)} is called.
*/
public DeferredResult(Object timeoutResult) {
this.timeoutResult = timeoutResult;
}
boolean canHandleTimeout() {
return this.timeoutResult != TIMEOUT_RESULT_NONE;
public DeferredResult(V timeoutValue) {
this.timeoutValue = timeoutValue;
this.timeoutValueSet = true;
}
/**
* Complete async processing with the given result. If the DeferredResult is
* not yet fully initialized, this method will block and wait for that to
* Complete async processing with the given value. If the DeferredResult is
* not fully initialized yet, this method will block and wait for that to
* occur before proceeding. See the class level javadoc for more details.
*
* @throws StaleAsyncWebRequestException if the underlying async request
* has already timed out or ended due to a network error.
*/
public void set(Object result) throws StaleAsyncWebRequestException {
if (this.timeoutLock.tryLock() && (this.result != this.timeoutResult)) {
public void set(V value) throws StaleAsyncWebRequestException {
if (this.setLock.tryLock() && (!this.timeoutValueUsed)) {
try {
handle(result);
handle(value);
}
finally {
this.timeoutLock.unlock();
this.setLock.unlock();
}
}
else {
// A timeout is in progress
throw new StaleAsyncWebRequestException("Async request already timed out");
// A timeout is in progress or has already occurred
throw new StaleAsyncWebRequestException("Async request timed out");
}
}
/**
* A variant of {@link #set(Object)} that absorbs a potential, resulting
* An alternative to {@link #set(Object)} that absorbs a potential
* {@link StaleAsyncWebRequestException}.
* @return {@code false} if the outcome was a {@code StaleAsyncWebRequestException}
*/
public boolean trySet(Object result) throws StaleAsyncWebRequestException {
public boolean trySet(V result) throws StaleAsyncWebRequestException {
try {
set(result);
return true;
@@ -119,29 +119,12 @@ public final class DeferredResult {
return false;
}
/**
* Invoked to complete async processing when a timeout occurs before
* {@link #set(Object)} is called. Or if {@link #set(Object)} is already in
* progress, this method blocks, waits for it to complete, and then returns.
*/
void handleTimeout() {
Assert.state(canHandleTimeout(), "Can't handle timeout");
this.timeoutLock.lock();
try {
if (this.result == null) {
handle(this.timeoutResult);
}
}
finally {
this.timeoutLock.unlock();
}
}
private void handle(Object result) throws StaleAsyncWebRequestException {
private void handle(V result) throws StaleAsyncWebRequestException {
Assert.isNull(this.result, "A deferred result can be set once only");
this.result = result;
this.timeoutValueUsed = (this.timeoutValueSet && (this.result == this.timeoutValue));
try {
this.readySignal.await(10, TimeUnit.SECONDS);
this.initializationLatch.await(10, TimeUnit.SECONDS);
}
catch (InterruptedException e) {
throw new IllegalStateException(
@@ -153,9 +136,35 @@ public final class DeferredResult {
this.resultHandler.handle(result);
}
/**
* Return a handler to use to complete processing using the default timeout value
* provided via {@link #DeferredResult(Object)} or {@code null} if no timeout
* value was provided.
*/
Runnable getTimeoutHandler() {
if (!this.timeoutValueSet) {
return null;
}
return new Runnable() {
public void run() { useTimeoutValue(); }
};
}
private void useTimeoutValue() {
this.setLock.lock();
try {
if (this.result == null) {
handle(this.timeoutValue);
this.timeoutValueUsed = true;
}
} finally {
this.setLock.unlock();
}
}
void init(DeferredResultHandler handler) {
this.resultHandler = handler;
this.readySignal.countDown();
this.initializationLatch.countDown();
}

View File

@@ -144,7 +144,7 @@ public class AsyncExecutionChainTests {
this.chain.addDelegatingCallable(new IntegerIncrementingCallable());
this.chain.addDelegatingCallable(new IntegerIncrementingCallable());
DeferredResult deferredResult = new DeferredResult();
DeferredResult<Integer> deferredResult = new DeferredResult<Integer>();
this.chain.startDeferredResultProcessing(deferredResult);
assertTrue(this.asyncWebRequest.isAsyncStarted());
@@ -159,7 +159,7 @@ public class AsyncExecutionChainTests {
this.asyncWebRequest.startAsync();
this.asyncWebRequest.complete();
DeferredResult deferredResult = new DeferredResult();
DeferredResult<Integer> deferredResult = new DeferredResult<Integer>();
this.chain.startDeferredResultProcessing(deferredResult);
deferredResult.set(1);
}

View File

@@ -20,8 +20,8 @@ import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.reset;
import static org.easymock.EasyMock.verify;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import org.junit.Test;
@@ -34,16 +34,10 @@ import org.springframework.web.context.request.async.DeferredResult.DeferredResu
*/
public class DeferredResultTests {
@Test
public void canHandleTimeout() {
assertFalse(new DeferredResult().canHandleTimeout());
assertTrue(new DeferredResult("foo").canHandleTimeout());
}
@Test
public void set() {
DeferredResultHandler resultHandler = createMock(DeferredResultHandler.class);
DeferredResult deferredResult = new DeferredResult();
DeferredResult<String> deferredResult = new DeferredResult<String>();
deferredResult.init(resultHandler);
resultHandler.handle("foo");
@@ -54,35 +48,36 @@ public class DeferredResultTests {
verify(resultHandler);
}
@Test
public void getTimeoutHandler() {
assertNull(new DeferredResult<String>().getTimeoutHandler());
assertNotNull(new DeferredResult<String>("foo").getTimeoutHandler());
}
@Test
public void handleTimeout() {
DeferredResultHandler resultHandler = createMock(DeferredResultHandler.class);
DeferredResult deferredResult = new DeferredResult("foo");
deferredResult.init(resultHandler);
resultHandler.handle("foo");
replay(resultHandler);
deferredResult.handleTimeout();
DeferredResult<String> deferredResult = new DeferredResult<String>("foo");
deferredResult.init(resultHandler);
deferredResult.getTimeoutHandler().run();
verify(resultHandler);
}
@Test(expected=IllegalStateException.class)
public void handleTimeout_timeoutResultNone() {
new DeferredResult().handleTimeout();
}
@Test
public void setAfterHandleTimeout() {
public void setAfterTimeoutValueUsed() {
DeferredResultHandler resultHandler = createMock(DeferredResultHandler.class);
DeferredResult deferredResult = new DeferredResult("foo");
deferredResult.init(resultHandler);
resultHandler.handle("foo");
replay(resultHandler);
deferredResult.handleTimeout();
DeferredResult<String> deferredResult = new DeferredResult<String>("foo");
deferredResult.init(resultHandler);
deferredResult.getTimeoutHandler().run();
verify(resultHandler);
@@ -96,14 +91,13 @@ public class DeferredResultTests {
}
@Test
public void setBeforeHandleTimeout() {
public void setBeforeTimeoutValueUsed() {
DeferredResultHandler resultHandler = createMock(DeferredResultHandler.class);
DeferredResult deferredResult = new DeferredResult("foo");
deferredResult.init(resultHandler);
resultHandler.handle("foo");
replay(resultHandler);
DeferredResult<String> deferredResult = new DeferredResult<String>("foo");
deferredResult.init(resultHandler);
deferredResult.set("foo");
verify(resultHandler);
@@ -111,7 +105,7 @@ public class DeferredResultTests {
reset(resultHandler);
replay(resultHandler);
deferredResult.handleTimeout();
deferredResult.getTimeoutHandler().run();
verify(resultHandler);
}

View File

@@ -63,7 +63,7 @@ public class AsyncMethodReturnValueHandler implements HandlerMethodReturnValueHa
chain.startCallableChainProcessing();
}
else if (DeferredResult.class.isAssignableFrom(paramType)) {
chain.startDeferredResultProcessing((DeferredResult) returnValue);
chain.startDeferredResultProcessing((DeferredResult<?>) returnValue);
}
else {
// should never happen..

View File

@@ -22,6 +22,7 @@ Changes in version 3.2 M2 (2012-08-xx)
* add @ExceptionResolver annotation to detect classes with @ExceptionHandler methods
* move RSS/Atom message converter registration ahead of jackson/jaxb2
* handle BindException in DefaultHandlerExceptionResolver
* Parameterize DefaultResult type
Changes in version 3.2 M1 (2012-05-28)