/*
* Copyright 2002-2018 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.PriorityQueue;
import java.util.concurrent.Callable;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.web.context.request.NativeWebRequest;
DeferredResult
provides an alternative to using a Callable
for asynchronous request processing. While a Callable
is executed concurrently on behalf of the application, with a DeferredResult
the application can produce the result from a thread of its choice. Subclasses can extend this class to easily associate additional data or behavior with the DeferredResult
. For example, one might want to associate the user used to create the DeferredResult
by extending the class and adding an additional property for the user. In this way, the user could easily be accessed later without the need to use a data structure to do the mapping.
An example of associating additional behavior to this class might be realized by extending the class to implement an additional interface. For example, one might want to implement Comparable
so that when the DeferredResult
is added to a PriorityQueue
it is handled in the correct order.
Author: Rossen Stoyanchev, Juergen Hoeller, Rob Winch Type parameters: - <T> – the result type
Since: 3.2
/**
* {@code DeferredResult} provides an alternative to using a {@link Callable} for
* asynchronous request processing. While a {@code Callable} is executed concurrently
* on behalf of the application, with a {@code DeferredResult} the application can
* produce the result from a thread of its choice.
*
* <p>Subclasses can extend this class to easily associate additional data or behavior
* with the {@link DeferredResult}. For example, one might want to associate the user
* used to create the {@link DeferredResult} by extending the class and adding an
* additional property for the user. In this way, the user could easily be accessed
* later without the need to use a data structure to do the mapping.
*
* <p>An example of associating additional behavior to this class might be realized
* by extending the class to implement an additional interface. For example, one
* might want to implement {@link Comparable} so that when the {@link DeferredResult}
* is added to a {@link PriorityQueue} it is handled in the correct order.
*
* @author Rossen Stoyanchev
* @author Juergen Hoeller
* @author Rob Winch
* @since 3.2
* @param <T> the result type
*/
public class DeferredResult<T> {
private static final Object RESULT_NONE = new Object();
private static final Log logger = LogFactory.getLog(DeferredResult.class);
@Nullable
private final Long timeout;
private final Supplier<?> timeoutResult;
private Runnable timeoutCallback;
private Consumer<Throwable> errorCallback;
private Runnable completionCallback;
private DeferredResultHandler resultHandler;
private volatile Object result = RESULT_NONE;
private volatile boolean expired = false;
Create a DeferredResult.
/**
* Create a DeferredResult.
*/
public DeferredResult() {
this(null, () -> RESULT_NONE);
}
Create a DeferredResult with a custom timeout value.
By default not set in which case the default configured in the MVC
Java Config or the MVC namespace is used, or if that's not set, then the
timeout depends on the default of the underlying server.
Params: - timeout – timeout value in milliseconds
/**
* Create a DeferredResult with a custom timeout value.
* <p>By default not set in which case the default configured in the MVC
* Java Config or the MVC namespace is used, or if that's not set, then the
* timeout depends on the default of the underlying server.
* @param timeout timeout value in milliseconds
*/
public DeferredResult(Long timeout) {
this(timeout, () -> RESULT_NONE);
}
Create a DeferredResult with a timeout value and a default result to use
in case of timeout.
Params: - timeout – timeout value in milliseconds (ignored if
null
) - timeoutResult – the result to use
/**
* Create a DeferredResult with a timeout value and a default result to use
* in case of timeout.
* @param timeout timeout value in milliseconds (ignored if {@code null})
* @param timeoutResult the result to use
*/
public DeferredResult(@Nullable Long timeout, final Object timeoutResult) {
this.timeoutResult = () -> timeoutResult;
this.timeout = timeout;
}
Variant of DeferredResult(Long, Object)
that accepts a dynamic fallback value based on a Supplier
. Params: - timeout – timeout value in milliseconds (ignored if
null
) - timeoutResult – the result supplier to use
Since: 5.1.1
/**
* Variant of {@link #DeferredResult(Long, Object)} that accepts a dynamic
* fallback value based on a {@link Supplier}.
* @param timeout timeout value in milliseconds (ignored if {@code null})
* @param timeoutResult the result supplier to use
* @since 5.1.1
*/
public DeferredResult(@Nullable Long timeout, Supplier<?> timeoutResult) {
this.timeoutResult = timeoutResult;
this.timeout = timeout;
}
Return true
if this DeferredResult is no longer usable either because it was previously set or because the underlying request expired. The result may have been set with a call to setResult(Object)
, or setErrorResult(Object)
, or as a result of a timeout, if a timeout result was provided to the constructor. The request may also expire due to a timeout or network error.
/**
* Return {@code true} if this DeferredResult is no longer usable either
* because it was previously set or because the underlying request expired.
* <p>The result may have been set with a call to {@link #setResult(Object)},
* or {@link #setErrorResult(Object)}, or as a result of a timeout, if a
* timeout result was provided to the constructor. The request may also
* expire due to a timeout or network error.
*/
public final boolean isSetOrExpired() {
return (this.result != RESULT_NONE || this.expired);
}
Return true
if the DeferredResult has been set. Since: 4.0
/**
* Return {@code true} if the DeferredResult has been set.
* @since 4.0
*/
public boolean hasResult() {
return (this.result != RESULT_NONE);
}
Return the result, or null
if the result wasn't set. Since the result can also be null
, it is recommended to use hasResult()
first to check if there is a result prior to calling this method. Since: 4.0
/**
* Return the result, or {@code null} if the result wasn't set. Since the result
* can also be {@code null}, it is recommended to use {@link #hasResult()} first
* to check if there is a result prior to calling this method.
* @since 4.0
*/
@Nullable
public Object getResult() {
Object resultToCheck = this.result;
return (resultToCheck != RESULT_NONE ? resultToCheck : null);
}
Return the configured timeout value in milliseconds.
/**
* Return the configured timeout value in milliseconds.
*/
@Nullable
final Long getTimeoutValue() {
return this.timeout;
}
Register code to invoke when the async request times out.
This method is called from a container thread when an async request times out before the DeferredResult
has been populated. It may invoke setResult
or setErrorResult
to resume processing.
/**
* Register code to invoke when the async request times out.
* <p>This method is called from a container thread when an async request
* times out before the {@code DeferredResult} has been populated.
* It may invoke {@link DeferredResult#setResult setResult} or
* {@link DeferredResult#setErrorResult setErrorResult} to resume processing.
*/
public void onTimeout(Runnable callback) {
this.timeoutCallback = callback;
}
Register code to invoke when an error occurred during the async request.
This method is called from a container thread when an error occurs while processing an async request before the DeferredResult
has been populated. It may invoke setResult
or setErrorResult
to resume processing.
Since: 5.0
/**
* Register code to invoke when an error occurred during the async request.
* <p>This method is called from a container thread when an error occurs
* 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.
* @since 5.0
*/
public void onError(Consumer<Throwable> callback) {
this.errorCallback = callback;
}
Register code to invoke when the async request completes.
This method is called from a container thread when an async request completed for any reason including timeout and network error. This is useful for detecting that a DeferredResult
instance is no longer usable.
/**
* Register code to invoke when the async request completes.
* <p>This method is called from a container thread when an async request
* completed for any reason including timeout and network error. This is useful
* for detecting that a {@code DeferredResult} instance is no longer usable.
*/
public void onCompletion(Runnable callback) {
this.completionCallback = callback;
}
Provide a handler to use to handle the result value.
Params: - resultHandler – the handler
See Also:
/**
* Provide a handler to use to handle the result value.
* @param resultHandler the handler
* @see DeferredResultProcessingInterceptor
*/
public final void setResultHandler(DeferredResultHandler resultHandler) {
Assert.notNull(resultHandler, "DeferredResultHandler is required");
// Immediate expiration check outside of the result lock
if (this.expired) {
return;
}
Object resultToHandle;
synchronized (this) {
// Got the lock in the meantime: double-check expiration status
if (this.expired) {
return;
}
resultToHandle = this.result;
if (resultToHandle == RESULT_NONE) {
// No result yet: store handler for processing once it comes in
this.resultHandler = resultHandler;
return;
}
}
// If we get here, we need to process an existing result object immediately.
// The decision is made within the result lock; just the handle call outside
// of it, avoiding any deadlock potential with Servlet container locks.
try {
resultHandler.handleResult(resultToHandle);
}
catch (Throwable ex) {
logger.debug("Failed to process async result", ex);
}
}
Set the value for the DeferredResult and handle it.
Params: - result – the value to set
See Also: Returns: true
if the result was set and passed on for handling; false
if the result was already set or the async request expired
/**
* Set the value for the DeferredResult and handle it.
* @param result the value to set
* @return {@code true} if the result was set and passed on for handling;
* {@code false} if the result was already set or the async request expired
* @see #isSetOrExpired()
*/
public boolean setResult(T result) {
return setResultInternal(result);
}
private boolean setResultInternal(Object result) {
// Immediate expiration check outside of the result lock
if (isSetOrExpired()) {
return false;
}
DeferredResultHandler resultHandlerToUse;
synchronized (this) {
// Got the lock in the meantime: double-check expiration status
if (isSetOrExpired()) {
return false;
}
// At this point, we got a new result to process
this.result = result;
resultHandlerToUse = this.resultHandler;
if (resultHandlerToUse == null) {
// No result handler set yet -> let the setResultHandler implementation
// pick up the result object and invoke the result handler for it.
return true;
}
// Result handler available -> let's clear the stored reference since
// we don't need it anymore.
this.resultHandler = null;
}
// If we get here, we need to process an existing result object immediately.
// The decision is made within the result lock; just the handle call outside
// of it, avoiding any deadlock potential with Servlet container locks.
resultHandlerToUse.handleResult(result);
return true;
}
Set an error value for the DeferredResult
and handle it. The value may be an Exception
or Throwable
in which case it will be processed as if a handler raised the exception. Params: - result – the error result value
See Also: Returns: true
if the result was set to the error value and passed on for handling; false
if the result was already set or the async request expired
/**
* Set an error value for the {@link DeferredResult} and handle it.
* The value may be an {@link Exception} or {@link Throwable} in which case
* it will be processed as if a handler raised the exception.
* @param result the error result value
* @return {@code true} if the result was set to the error value and passed on
* for handling; {@code false} if the result was already set or the async
* request expired
* @see #isSetOrExpired()
*/
public boolean setErrorResult(Object result) {
return setResultInternal(result);
}
final DeferredResultProcessingInterceptor getInterceptor() {
return new DeferredResultProcessingInterceptor() {
@Override
public <S> boolean handleTimeout(NativeWebRequest request, DeferredResult<S> deferredResult) {
boolean continueProcessing = true;
try {
if (timeoutCallback != null) {
timeoutCallback.run();
}
}
finally {
Object value = timeoutResult.get();
if (value != RESULT_NONE) {
continueProcessing = false;
try {
setResultInternal(value);
}
catch (Throwable ex) {
logger.debug("Failed to handle timeout result", ex);
}
}
}
return continueProcessing;
}
@Override
public <S> boolean handleError(NativeWebRequest request, DeferredResult<S> deferredResult, Throwable t) {
try {
if (errorCallback != null) {
errorCallback.accept(t);
}
}
finally {
try {
setResultInternal(t);
}
catch (Throwable ex) {
logger.debug("Failed to handle error result", ex);
}
}
return false;
}
@Override
public <S> void afterCompletion(NativeWebRequest request, DeferredResult<S> deferredResult) {
expired = true;
if (completionCallback != null) {
completionCallback.run();
}
}
};
}
Handles a DeferredResult value when set.
/**
* Handles a DeferredResult value when set.
*/
@FunctionalInterface
public interface DeferredResultHandler {
void handleResult(Object result);
}
}