Add onError callback to DeferredResult
Issue: SPR-15614
This commit is contained in:
committed by
Rossen Stoyanchev
parent
140542e8b1
commit
e0678ba583
@@ -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);
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user