Index: java/org/apache/catalina/core/AsyncContextImpl.java =================================================================== --- java/org/apache/catalina/core/AsyncContextImpl.java (revision 1849198) +++ java/org/apache/catalina/core/AsyncContextImpl.java (working copy) @@ -111,6 +111,7 @@ } finally { context.fireRequestDestroyEvent(request.getRequest()); clearServletRequestResponse(); + this.context.decrementInProgressAsyncCount(); context.unbind(Globals.IS_SECURITY_ENABLED, oldCL); } } @@ -205,6 +206,7 @@ request, applicationDispatcher, servletRequest, servletResponse); this.request.getCoyoteRequest().action(ActionCode.ASYNC_DISPATCH, null); clearServletRequestResponse(); + this.context.decrementInProgressAsyncCount(); } } @@ -311,6 +313,7 @@ ActionCode.ASYNC_START, this); this.context = context; + context.incrementInProgressAsyncCount(); this.servletRequest = request; this.servletResponse = response; this.hasOriginalRequestAndResponse = originalRequestResponse; @@ -377,6 +380,17 @@ } + + @Override + public boolean isAvailable() { + Context context = this.context; + if (context == null) { + return false; + } + return context.getState().isAvailable(); + } + + public void setErrorState(Throwable t, boolean fireOnError) { if (t!=null) request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, t); request.getCoyoteRequest().action(ActionCode.ASYNC_ERROR, null); Index: java/org/apache/catalina/core/StandardContext.java =================================================================== --- java/org/apache/catalina/core/StandardContext.java (revision 1849198) +++ java/org/apache/catalina/core/StandardContext.java (working copy) @@ -820,10 +820,24 @@ private boolean allowMultipleLeadingForwardSlashInPath = false; + private final AtomicLong inProgressAsyncCount = new AtomicLong(0); + // ----------------------------------------------------- Context Properties @Override + public void incrementInProgressAsyncCount() { + inProgressAsyncCount.incrementAndGet(); + } + + + @Override + public void decrementInProgressAsyncCount() { + inProgressAsyncCount.decrementAndGet(); + } + + + @Override public void setAllowMultipleLeadingForwardSlashInPath( boolean allowMultipleLeadingForwardSlashInPath) { this.allowMultipleLeadingForwardSlashInPath = allowMultipleLeadingForwardSlashInPath; @@ -5325,6 +5339,22 @@ broadcaster.sendNotification(notification); } + // Context has been removed from Mapper at this point (so no new + // requests will be mapped) but is still available. + + // Give the in progress async requests a chance to complete + long limit = System.currentTimeMillis() + unloadDelay; + while (inProgressAsyncCount.get() > 0 && System.currentTimeMillis() < limit) { + try { + Thread.sleep(50); + } catch (InterruptedException e) { + // TODO + log.info(sm.getString(""), e); + } + } + + // Once the state is set to STOPPING, the Context will report itself as + // not available and any in progress async requests will timeout setState(LifecycleState.STOPPING); // Binding thread Index: java/org/apache/catalina/startup/FailedContext.java =================================================================== --- java/org/apache/catalina/startup/FailedContext.java (revision 1849198) +++ java/org/apache/catalina/startup/FailedContext.java (working copy) @@ -809,4 +809,9 @@ } @Override public boolean getAllowMultipleLeadingForwardSlashInPath() { return false; } + + @Override + public void incrementInProgressAsyncCount() { /* NO-OP */ } + @Override + public void decrementInProgressAsyncCount() { /* NO-OP */ } } \ No newline at end of file Index: java/org/apache/catalina/Context.java =================================================================== --- java/org/apache/catalina/Context.java (revision 1849198) +++ java/org/apache/catalina/Context.java (working copy) @@ -1863,4 +1863,10 @@ * otherwise false */ public boolean getAllowMultipleLeadingForwardSlashInPath(); + + + public void incrementInProgressAsyncCount(); + + + public void decrementInProgressAsyncCount(); } Index: java/org/apache/coyote/AbstractProcessor.java =================================================================== --- java/org/apache/coyote/AbstractProcessor.java (revision 1849198) +++ java/org/apache/coyote/AbstractProcessor.java (working copy) @@ -639,6 +639,10 @@ if ((now - asyncStart) > asyncTimeout) { doTimeoutAsync(); } + } else if (!asyncStateMachine.isAvailable()) { + // Timeout the async process if the associated web application + // is no longer running. + doTimeoutAsync(); } } } Index: java/org/apache/coyote/AsyncContextCallback.java =================================================================== --- java/org/apache/coyote/AsyncContextCallback.java (revision 1849198) +++ java/org/apache/coyote/AsyncContextCallback.java (working copy) @@ -17,12 +17,20 @@ package org.apache.coyote; /** - * Provides a mechanism for the Coyote connectors to signal to a - * {@link javax.servlet.AsyncContext} implementation that an action, such as - * firing event listeners needs to be taken. It is implemented in this manner - * so that the org.apache.coyote package does not have a dependency on the + * Provides a mechanism for the Coyote connectors to communicate with the + * {@link javax.servlet.AsyncContext}. It is implemented in this manner so that + * the org.apache.coyote package does not have a dependency on the * org.apache.catalina package. */ public interface AsyncContextCallback { public void fireOnComplete(); + + /** + * Reports if the web application associated with this async request is + * available. + * + * @return {@code true} if the associated web application is available, + * otherwise {@code false} + */ + public boolean isAvailable(); } Index: java/org/apache/coyote/AsyncStateMachine.java =================================================================== --- java/org/apache/coyote/AsyncStateMachine.java (revision 1849198) +++ java/org/apache/coyote/AsyncStateMachine.java (working copy) @@ -483,6 +483,16 @@ } + synchronized boolean isAvailable() { + if (asyncCtxt == null) { + // Async processing has probably been completed in another thread. + // Trigger a timeout to make sure the Processor is cleaned up. + return false; + } + return asyncCtxt.isAvailable(); + } + + synchronized void recycle() { // Use lastAsyncStart to determine if this instance has been used since // it was last recycled. If it hasn't there is no need to recycle again Index: test/org/apache/tomcat/unittest/TesterContext.java =================================================================== --- test/org/apache/tomcat/unittest/TesterContext.java (revision 1849198) +++ test/org/apache/tomcat/unittest/TesterContext.java (working copy) @@ -1274,4 +1274,9 @@ } @Override public boolean getAllowMultipleLeadingForwardSlashInPath() { return false; } + + @Override + public void incrementInProgressAsyncCount() { /* NO-OP */ } + @Override + public void decrementInProgressAsyncCount() { /* NO-OP */ } }