Add onError callback to DeferredResult

Issue: SPR-15614
This commit is contained in:
Violeta Georgieva
2017-06-23 16:48:40 +03:00
committed by Rossen Stoyanchev
parent 140542e8b1
commit e0678ba583
27 changed files with 771 additions and 41 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,8 @@
package org.springframework.web.context.request.async;
import java.util.function.Consumer;
import org.springframework.lang.Nullable;
import org.springframework.web.context.request.NativeWebRequest;
@@ -42,7 +44,13 @@ public interface AsyncWebRequest extends NativeWebRequest {
void addTimeoutHandler(Runnable runnable);
/**
* Add a handle to invoke when request processing completes.
* Add a handler to invoke when an error occurred while concurrent
* handling of a request.
*/
void addErrorHandler(Consumer<Throwable> exceptionHandler);
/**
* Add a handler to invoke when request processing completes.
*/
void addCompletionHandler(Runnable runnable);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -94,6 +94,24 @@ class CallableInterceptorChain {
return CallableProcessingInterceptor.RESULT_NONE;
}
public Object triggerAfterError(NativeWebRequest request, Callable<?> task, Throwable throwable) {
for (CallableProcessingInterceptor interceptor : this.interceptors) {
try {
Object result = interceptor.handleError(request, task, throwable);
if (result == CallableProcessingInterceptor.RESPONSE_HANDLED) {
break;
}
else if (result != CallableProcessingInterceptor.RESULT_NONE) {
return result;
}
}
catch (Throwable t) {
return t;
}
}
return CallableProcessingInterceptor.RESULT_NONE;
}
public void triggerAfterCompletion(NativeWebRequest request, Callable<?> task) {
for (int i = this.interceptors.size()-1; i >= 0; i--) {
try {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,7 +28,7 @@ import org.springframework.web.context.request.NativeWebRequest;
*
* <p>A {@code CallableProcessingInterceptor} is invoked before and after the
* invocation of the {@code Callable} task in the asynchronous thread, as well
* as on timeout from a container thread, or after completing for any reason
* as on timeout/error from a container thread, or after completing for any reason
* including a timeout or network error.
*
* <p>As a general rule exceptions raised by interceptor methods will cause
@@ -36,7 +36,7 @@ import org.springframework.web.context.request.NativeWebRequest;
* the Exception instance as the concurrent result. Such exceptions will then
* be processed through the {@code HandlerExceptionResolver} mechanism.
*
* <p>The {@link #handleTimeout(NativeWebRequest, Callable) afterTimeout} method
* <p>The {@link #handleTimeout(NativeWebRequest, Callable) handleTimeout} method
* can select a value to be used to resume processing.
*
* @author Rossen Stoyanchev
@@ -101,6 +101,21 @@ public interface CallableProcessingInterceptor {
*/
<T> Object handleTimeout(NativeWebRequest request, Callable<T> task) throws Exception;
/**
* Invoked from a container thread when an error occurred while processing the async request
* before the {@code Callable} task completes. Implementations may return a value,
* including an {@link Exception}, to use instead of the value the
* {@link Callable} did not return in time.
* @param request the current request
* @param task the task for the current async request
* @paramt t the error that occurred while request processing
* @return a concurrent result value; if the value is anything other than
* {@link #RESULT_NONE} or {@link #RESPONSE_HANDLED}, concurrent processing
* is resumed and subsequent interceptors are not invoked
* @throws Exception in case of errors
*/
<T> Object handleError(NativeWebRequest request, Callable<T> task, Throwable t) throws Exception;
/**
* Invoked from a container thread when async processing completes for any
* reason including timeout or network error.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -60,6 +60,15 @@ public abstract class CallableProcessingInterceptorAdapter implements CallablePr
return RESULT_NONE;
}
/**
* This implementation always returns
* {@link CallableProcessingInterceptor#RESULT_NONE RESULT_NONE}.
*/
@Override
public <T> Object handleError(NativeWebRequest request, Callable<T> task, Throwable t) throws Exception {
return RESULT_NONE;
}
/**
* This implementation is empty.
*/

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.web.context.request.async;
import java.util.PriorityQueue;
import java.util.concurrent.Callable;
import java.util.function.Consumer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -61,6 +62,8 @@ public class DeferredResult<T> {
private Runnable timeoutCallback;
private Consumer<Throwable> errorCallback;
private Runnable completionCallback;
private DeferredResultHandler resultHandler;
@@ -150,6 +153,17 @@ public class DeferredResult<T> {
this.timeoutCallback = callback;
}
/**
* Register code to invoke when an error occurred while processing the async request.
* <p>This method is called from a container thread when an error occurred while
* processing an async request before the {@code DeferredResult} has been populated.
* It may invoke {@link DeferredResult#setResult setResult} or
* {@link DeferredResult#setErrorResult setErrorResult} to resume processing.
*/
public void onError(Consumer<Throwable> callback) {
this.errorCallback = callback;
}
/**
* Register code to invoke when the async request completes.
* <p>This method is called from a container thread when an async request
@@ -275,6 +289,25 @@ public class DeferredResult<T> {
return continueProcessing;
}
@Override
public <S> boolean handleError(NativeWebRequest request, DeferredResult<S> deferredResult, Throwable t) {
boolean continueProcessing = true;
try {
if (errorCallback != null) {
errorCallback.accept(t);
}
}
finally {
continueProcessing = false;
try {
setResultInternal(t);
}
catch (Throwable ex) {
logger.debug("Failed to handle error result", ex);
}
}
return continueProcessing;
}
@Override
public <S> void afterCompletion(NativeWebRequest request, DeferredResult<S> deferredResult) {
expired = true;
if (completionCallback != null) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -78,6 +78,17 @@ class DeferredResultInterceptorChain {
}
}
public void triggerAfterError(NativeWebRequest request, DeferredResult<?> deferredResult, Throwable t) throws Exception {
for (DeferredResultProcessingInterceptor interceptor : this.interceptors) {
if (deferredResult.isSetOrExpired()) {
return;
}
if (!interceptor.handleError(request, deferredResult, t)){
break;
}
}
}
public void triggerAfterCompletion(NativeWebRequest request, DeferredResult<?> deferredResult) {
for (int i = this.preProcessingIndex; i >= 0; i--) {
try {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,7 +25,7 @@ import org.springframework.web.context.request.NativeWebRequest;
*
* <p>A {@code DeferredResultProcessingInterceptor} is invoked before the start
* of async processing, after the {@code DeferredResult} is set as well as on
* timeout, or after completing for any reason including a timeout or network
* timeout/error, or after completing for any reason including a timeout or network
* error.
*
* <p>As a general rule exceptions raised by interceptor methods will cause
@@ -33,7 +33,7 @@ import org.springframework.web.context.request.NativeWebRequest;
* the Exception instance as the concurrent result. Such exceptions will then
* be processed through the {@code HandlerExceptionResolver} mechanism.
*
* <p>The {@link #handleTimeout(NativeWebRequest, DeferredResult) afterTimeout}
* <p>The {@link #handleTimeout(NativeWebRequest, DeferredResult) handleTimeout}
* method can set the {@code DeferredResult} in order to resume processing.
*
* @author Rossen Stoyanchev
@@ -94,6 +94,22 @@ public interface DeferredResultProcessingInterceptor {
*/
<T> boolean handleTimeout(NativeWebRequest request, DeferredResult<T> deferredResult) throws Exception;
/**
* Invoked from a container thread when an error occurred while processing an async request
* before the {@code DeferredResult} has been set. Implementations may invoke
* {@link DeferredResult#setResult(Object) setResult} or
* {@link DeferredResult#setErrorResult(Object) setErrorResult} to resume processing.
* @param request the current request
* @param deferredResult the DeferredResult for the current request; if the
* {@code DeferredResult} is set, then concurrent processing is resumed and
* subsequent interceptors are not invoked
* @param t the error that occurred while request processing
* @return {@code true} if processing should continue, or {@code false} if
* other interceptors should not be invoked
* @throws Exception in case of errors
*/
<T> boolean handleError(NativeWebRequest request, DeferredResult<T> deferredResult, Throwable t) throws Exception;
/**
* Invoked from a container thread when an async request completed for any
* reason including timeout and network error. This method is useful for

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -60,6 +60,16 @@ public abstract class DeferredResultProcessingInterceptorAdapter implements Defe
return true;
}
/**
* This implementation returns {@code true} by default allowing other interceptors
* to be given a chance to handle the error.
*/
@Override
public <T> boolean handleError(NativeWebRequest request, DeferredResult<T> deferredResult, Throwable t)
throws Exception {
return true;
}
/**
* This implementation is empty.
*/

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.context.request.async;
import java.util.concurrent.Callable;
import org.springframework.web.context.request.NativeWebRequest;
/**
* Registered at the end, after all other interceptors and
* therefore invoked only if no other interceptor handles the error.
*
* @author Violeta Georgieva
* @since 5.0
*/
public class ErrorCallableProcessingInterceptor extends CallableProcessingInterceptorAdapter {
@Override
public <T> Object handleError(NativeWebRequest request, Callable<T> task, Throwable t) throws Exception {
return t;
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.context.request.async;
import org.springframework.web.context.request.NativeWebRequest;
/**
* Registered at the end, after all other interceptors and
* therefore invoked only if no other interceptor handles the error.
*
* @author Violeta Georgieva
* @since 5.0
*/
public class ErrorDeferredResultProcessingInterceptor extends DeferredResultProcessingInterceptorAdapter {
@Override
public <T> boolean handleError(NativeWebRequest request, DeferredResult<T> result, Throwable t)
throws Exception {
result.setErrorResult(t);
return false;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +20,8 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
@@ -50,6 +52,8 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
private final List<Runnable> timeoutHandlers = new ArrayList<>();
private final List<Consumer<Throwable>> exceptionHandlers = new ArrayList<>();
private final List<Runnable> completionHandlers = new ArrayList<>();
@@ -78,6 +82,11 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
this.timeoutHandlers.add(timeoutHandler);
}
@Override
public void addErrorHandler(Consumer<Throwable> exceptionHandler) {
this.exceptionHandlers.add(exceptionHandler);
}
@Override
public void addCompletionHandler(Runnable runnable) {
this.completionHandlers.add(runnable);
@@ -134,7 +143,9 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
@Override
public void onError(AsyncEvent event) throws IOException {
onComplete(event);
for (Consumer<Throwable> handler : this.exceptionHandlers) {
handler.accept(event.getThrowable());
}
}
@Override

View File

@@ -67,9 +67,15 @@ public final class WebAsyncManager {
private static final CallableProcessingInterceptor timeoutCallableInterceptor =
new TimeoutCallableProcessingInterceptor();
private static final CallableProcessingInterceptor errorCallableInterceptor =
new ErrorCallableProcessingInterceptor();
private static final DeferredResultProcessingInterceptor timeoutDeferredResultInterceptor =
new TimeoutDeferredResultProcessingInterceptor();
private static final DeferredResultProcessingInterceptor errorDeferredResultInterceptor =
new ErrorDeferredResultProcessingInterceptor();
private AsyncWebRequest asyncWebRequest;
@@ -281,6 +287,7 @@ public final class WebAsyncManager {
interceptors.add(webAsyncTask.getInterceptor());
interceptors.addAll(this.callableInterceptors.values());
interceptors.add(timeoutCallableInterceptor);
interceptors.add(errorCallableInterceptor);
final Callable<?> callable = webAsyncTask.getCallable();
final CallableInterceptorChain interceptorChain = new CallableInterceptorChain(interceptors);
@@ -293,6 +300,14 @@ public final class WebAsyncManager {
}
});
this.asyncWebRequest.addErrorHandler(t -> {
logger.debug("Processing error");
Object result = interceptorChain.triggerAfterError(this.asyncWebRequest, callable, t);
if (result != CallableProcessingInterceptor.RESULT_NONE) {
setConcurrentResultAndDispatch(result);
}
});
this.asyncWebRequest.addCompletionHandler(() ->
interceptorChain.triggerAfterCompletion(this.asyncWebRequest, callable));
@@ -371,6 +386,7 @@ public final class WebAsyncManager {
interceptors.add(deferredResult.getInterceptor());
interceptors.addAll(this.deferredResultInterceptors.values());
interceptors.add(timeoutDeferredResultInterceptor);
interceptors.add(errorDeferredResultInterceptor);
final DeferredResultInterceptorChain interceptorChain = new DeferredResultInterceptorChain(interceptors);
@@ -383,6 +399,15 @@ public final class WebAsyncManager {
}
});
this.asyncWebRequest.addErrorHandler(t -> {
try {
interceptorChain.triggerAfterError(this.asyncWebRequest, deferredResult, t);
}
catch (Throwable ex) {
setConcurrentResultAndDispatch(ex);
}
});
this.asyncWebRequest.addCompletionHandler(()
-> interceptorChain.triggerAfterCompletion(this.asyncWebRequest, deferredResult));

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -46,6 +46,8 @@ public class WebAsyncTask<V> implements BeanFactoryAware {
private Callable<V> timeoutCallback;
private Callable<V> errorCallback;
private Runnable completionCallback;
@@ -150,6 +152,18 @@ public class WebAsyncTask<V> implements BeanFactoryAware {
this.timeoutCallback = callback;
}
/**
* Register code to invoke when an error occurred while processing the async request.
* <p>This method is called from a container thread when an error occurred while processing
* an async request before the {@code Callable} has completed. The callback is executed in
* the same thread and therefore should return without blocking. It may return
* an alternative value to use, including an {@link Exception} or return
* {@link CallableProcessingInterceptor#RESULT_NONE RESULT_NONE}.
*/
public void onError(Callable<V> callback) {
this.errorCallback = callback;
}
/**
* Register code to invoke when the async request completes.
* <p>This method is called from a container thread when an async request
@@ -166,6 +180,10 @@ public class WebAsyncTask<V> implements BeanFactoryAware {
return (timeoutCallback != null ? timeoutCallback.call() : CallableProcessingInterceptor.RESULT_NONE);
}
@Override
public <T> Object handleError(NativeWebRequest request, Callable<T> task, Throwable t) throws Exception {
return (errorCallback != null ? errorCallback.call() : CallableProcessingInterceptor.RESULT_NONE);
}
@Override
public <T> void afterCompletion(NativeWebRequest request, Callable<T> task) throws Exception {
if (completionCallback != null) {
completionCallback.run();