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:
Rossen Stoyanchev
2012-04-23 11:20:17 -04:00
parent 699de7eb80
commit f37efb4279
7 changed files with 77 additions and 69 deletions

View File

@@ -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();
}
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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();
}
}