Polish Servlet async request processing
* Clarify semantics and behavior of AsyncWebRequest methods in most cases making a best effort and not raising an exception if async processing has completed for example due to a timeout. The startAsync() method is still protected with various checks and will raise ISE under a number of conditions. * Return 503 (service unavailable) when requests time out. * Logging improvements. Issue: SPR-8517
This commit is contained in:
@@ -35,7 +35,7 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public class AsyncExecutionChainRunnable implements Runnable {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(AsyncWebRequest.class);
|
||||
private static final Log logger = LogFactory.getLog(AsyncExecutionChainRunnable.class);
|
||||
|
||||
private final AsyncWebRequest asyncWebRequest;
|
||||
|
||||
@@ -62,8 +62,7 @@ public class AsyncExecutionChainRunnable implements Runnable {
|
||||
*/
|
||||
public void run() {
|
||||
try {
|
||||
logger.debug("Starting async execution chain");
|
||||
callable.call();
|
||||
this.callable.call();
|
||||
}
|
||||
catch (StaleAsyncWebRequestException ex) {
|
||||
logger.trace("Could not complete async request", ex);
|
||||
@@ -73,8 +72,8 @@ public class AsyncExecutionChainRunnable implements Runnable {
|
||||
this.asyncWebRequest.sendError(HttpStatus.INTERNAL_SERVER_ERROR, ex.getMessage());
|
||||
}
|
||||
finally {
|
||||
logger.debug("Exiting async execution chain");
|
||||
asyncWebRequest.complete();
|
||||
logger.debug("Completing async request processing");
|
||||
this.asyncWebRequest.complete();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,9 +21,7 @@ import org.springframework.web.context.request.NativeWebRequest;
|
||||
|
||||
|
||||
/**
|
||||
* Extends {@link NativeWebRequest} with methods for starting, completing, and
|
||||
* configuring async request processing. Abstract underlying mechanisms such as
|
||||
* the Servlet 3.0 AsyncContext.
|
||||
* Extend {@link NativeWebRequest} with methods for async request processing.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.2
|
||||
@@ -31,44 +29,44 @@ import org.springframework.web.context.request.NativeWebRequest;
|
||||
public interface AsyncWebRequest extends NativeWebRequest {
|
||||
|
||||
/**
|
||||
* Set the timeout for asynchronous request processing. When the timeout
|
||||
* begins depends on the underlying technology. With the Servlet 3 async
|
||||
* support the timeout begins after the main processing thread has exited
|
||||
* and has been returned to the container pool.
|
||||
* Set the timeout for asynchronous request processing in milliseconds.
|
||||
* In Servlet 3 async request processing, the timeout begins when the
|
||||
* main processing thread has exited.
|
||||
*/
|
||||
void setTimeout(Long timeout);
|
||||
|
||||
/**
|
||||
* Marks the start of async request processing for example. Ensures the
|
||||
* request remains open to be completed in a separate thread.
|
||||
* Mark the start of async request processing for example ensuring the
|
||||
* request remains open in order to be completed in a separate thread.
|
||||
* @throws IllegalStateException if async processing has started, if it is
|
||||
* not supported, or if it has completed.
|
||||
*/
|
||||
void startAsync();
|
||||
|
||||
/**
|
||||
* Return {@code true} if async processing has started following a call to
|
||||
* {@link #startAsync()} and before it has completed.
|
||||
* Whether async processing is in progress and has not yet completed.
|
||||
*/
|
||||
boolean isAsyncStarted();
|
||||
|
||||
/**
|
||||
* Complete async request processing finalizing the underlying request.
|
||||
* Complete async request processing making a best effort but without any
|
||||
* effect if async request processing has already completed for any reason
|
||||
* including a timeout.
|
||||
*/
|
||||
void complete();
|
||||
|
||||
/**
|
||||
* Send an error to the client.
|
||||
*/
|
||||
void sendError(HttpStatus status, String message);
|
||||
|
||||
/**
|
||||
* Return {@code true} if async processing completed either normally or for
|
||||
* any other reason such as a timeout or an error. Note that a timeout or a
|
||||
* (client) error may occur in a separate thread while async processing is
|
||||
* still in progress in its own thread. Hence the underlying async request
|
||||
* may become stale and this method may return {@code true} even if
|
||||
* {@link #complete()} was never actually called.
|
||||
* @see StaleAsyncWebRequestException
|
||||
* Whether async processing has completed either normally via calls to
|
||||
* {@link #complete()} or for other reasons such as a timeout likely
|
||||
* detected in a separate thread during async request processing.
|
||||
*/
|
||||
boolean isAsyncCompleted();
|
||||
|
||||
/**
|
||||
* Send an error to the client making a best effort to do so but without any
|
||||
* effect if async request processing has already completed, for example due
|
||||
* to a timeout.
|
||||
*/
|
||||
void sendError(HttpStatus status, String message);
|
||||
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ public class StaleAsyncRequestCheckingCallable extends AbstractDelegatingCallabl
|
||||
Object result = getNextCallable().call();
|
||||
if (this.asyncWebRequest.isAsyncCompleted()) {
|
||||
throw new StaleAsyncWebRequestException(
|
||||
"Async request no longer available due to a timed out or a (client) error");
|
||||
"Async request no longer available due to a timeout or a (client) error");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -32,10 +32,10 @@ import org.springframework.web.context.request.ServletWebRequest;
|
||||
/**
|
||||
* A Servlet 3.0 implementation of {@link AsyncWebRequest}.
|
||||
*
|
||||
* <p>The servlet processing an async request as well as all filters involved
|
||||
* must async support enabled. This can be done in Java using the Servlet API
|
||||
* or by adding an {@code <async-support>true</async-support>} element to
|
||||
* servlet and filter declarations in web.xml
|
||||
* <p>The servlet and all filters involved in an async request must have async
|
||||
* support enabled using the Servlet API or by adding an
|
||||
* {@code <async-support>true</async-support>} element to servlet and filter
|
||||
* declarations in web.xml
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.2
|
||||
@@ -57,7 +57,6 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
|
||||
}
|
||||
|
||||
public boolean isAsyncStarted() {
|
||||
assertNotStale();
|
||||
return ((this.asyncContext != null) && getRequest().isAsyncStarted());
|
||||
}
|
||||
|
||||
@@ -81,12 +80,17 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
|
||||
}
|
||||
|
||||
public void complete() {
|
||||
assertNotStale();
|
||||
if (!isAsyncCompleted()) {
|
||||
this.asyncContext.complete();
|
||||
completeInternal();
|
||||
}
|
||||
}
|
||||
|
||||
private void completeInternal() {
|
||||
this.asyncContext = null;
|
||||
this.asyncCompleted.set(true);
|
||||
}
|
||||
|
||||
public void sendError(HttpStatus status, String message) {
|
||||
try {
|
||||
if (!isAsyncCompleted()) {
|
||||
@@ -107,18 +111,19 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
public void onTimeout(AsyncEvent event) throws IOException {
|
||||
this.asyncCompleted.set(true);
|
||||
completeInternal();
|
||||
getResponse().sendError(HttpStatus.SERVICE_UNAVAILABLE.value());
|
||||
}
|
||||
|
||||
public void onError(AsyncEvent event) throws IOException {
|
||||
this.asyncCompleted.set(true);
|
||||
completeInternal();
|
||||
}
|
||||
|
||||
public void onStartAsync(AsyncEvent event) throws IOException {
|
||||
}
|
||||
|
||||
public void onComplete(AsyncEvent event) throws IOException {
|
||||
this.asyncCompleted.set(true);
|
||||
completeInternal();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user