Author: markt Date: Tue Nov 13 23:55:26 2012 New Revision: 1409036 URL: http://svn.apache.org/viewvc?rev=1409036&view=rev Log: More updates to the async error handling triggered by kkolinko's review - simplify check to see if listeners changed async state during onTimeout - add option to control if onError fires during error handling (it doesn't if called from the timeout code since that has its own event) - add check to see if listeners changed state during onError - add check to see if state changed during "error dispatch" - remove AsyncState.ERROR as a valid start state for asyncPostProcess() - aligned the unit tests (one change) with the new behaviour
Not tested with the TCK. Modified: tomcat/tc7.0.x/trunk/ (props changed) tomcat/tc7.0.x/trunk/java/org/apache/catalina/connector/CoyoteAdapter.java tomcat/tc7.0.x/trunk/java/org/apache/catalina/core/AsyncContextImpl.java tomcat/tc7.0.x/trunk/java/org/apache/coyote/ActionCode.java tomcat/tc7.0.x/trunk/java/org/apache/coyote/AsyncStateMachine.java tomcat/tc7.0.x/trunk/java/org/apache/coyote/ajp/AbstractAjpProcessor.java tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/AbstractHttp11Processor.java tomcat/tc7.0.x/trunk/test/org/apache/catalina/core/TestAsyncContextImpl.java Propchange: tomcat/tc7.0.x/trunk/ ------------------------------------------------------------------------------ Merged /tomcat/trunk:r1409030 Modified: tomcat/tc7.0.x/trunk/java/org/apache/catalina/connector/CoyoteAdapter.java URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/catalina/connector/CoyoteAdapter.java?rev=1409036&r1=1409035&r2=1409036&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/java/org/apache/catalina/connector/CoyoteAdapter.java (original) +++ tomcat/tc7.0.x/trunk/java/org/apache/catalina/connector/CoyoteAdapter.java Tue Nov 13 23:55:26 2012 @@ -290,7 +290,7 @@ public class CoyoteAdapter implements Ad if (status==SocketStatus.TIMEOUT) { success = true; if (!asyncConImpl.timeout()) { - asyncConImpl.setErrorState(null); + asyncConImpl.setErrorState(null, false); } } if (request.isAsyncDispatching()) { @@ -299,7 +299,7 @@ public class CoyoteAdapter implements Ad Throwable t = (Throwable) request.getAttribute( RequestDispatcher.ERROR_EXCEPTION); if (t != null) { - asyncConImpl.setErrorState(t); + asyncConImpl.setErrorState(t, true); } } Modified: tomcat/tc7.0.x/trunk/java/org/apache/catalina/core/AsyncContextImpl.java URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/catalina/core/AsyncContextImpl.java?rev=1409036&r1=1409035&r2=1409036&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/java/org/apache/catalina/core/AsyncContextImpl.java (original) +++ tomcat/tc7.0.x/trunk/java/org/apache/catalina/core/AsyncContextImpl.java Tue Nov 13 23:55:26 2012 @@ -116,7 +116,7 @@ public class AsyncContextImpl implements } } - public boolean timeout() throws IOException { + public boolean timeout() { AtomicBoolean result = new AtomicBoolean(); request.getCoyoteRequest().action(ActionCode.ASYNC_TIMEOUT, result); @@ -126,23 +126,20 @@ public class AsyncContextImpl implements ClassLoader newCL = request.getContext().getLoader().getClassLoader(); try { Thread.currentThread().setContextClassLoader(newCL); - boolean listenerInvoked = false; List<AsyncListenerWrapper> listenersCopy = new ArrayList<AsyncListenerWrapper>(); listenersCopy.addAll(listeners); for (AsyncListenerWrapper listener : listenersCopy) { - listener.fireOnTimeout(event); - listenerInvoked = true; + try { + listener.fireOnTimeout(event); + } catch (IOException ioe) { + log.warn("onTimeout() failed for listener of type [" + + listener.getClass().getName() + "]", ioe); + } } - if (listenerInvoked) { - request.getCoyoteRequest().action( - ActionCode.ASYNC_IS_TIMINGOUT, result); - return !result.get(); - } else { - // No listeners, trigger error handling - return false; - } - + request.getCoyoteRequest().action( + ActionCode.ASYNC_IS_TIMINGOUT, result); + return !result.get(); } finally { Thread.currentThread().setContextClassLoader(oldCL); } @@ -246,7 +243,6 @@ public class AsyncContextImpl implements listeners.add(wrapper); } - @SuppressWarnings("unchecked") @Override public <T extends AsyncListener> T createListener(Class<T> clazz) throws ServletException { @@ -368,38 +364,51 @@ public class AsyncContextImpl implements } - public void setErrorState(Throwable t) { + public void setErrorState(Throwable t, boolean fireOnError) { if (t!=null) request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, t); request.getCoyoteRequest().action(ActionCode.ASYNC_ERROR, null); - AsyncEvent errorEvent = new AsyncEvent(event.getAsyncContext(), - event.getSuppliedRequest(), event.getSuppliedResponse(), t); - List<AsyncListenerWrapper> listenersCopy = - new ArrayList<AsyncListenerWrapper>(); - listenersCopy.addAll(listeners); - for (AsyncListenerWrapper listener : listenersCopy) { - try { - listener.fireOnError(errorEvent); - } catch (IOException ioe) { - log.warn("onError() failed for listener of type [" + - listener.getClass().getName() + "]", ioe); - } - } - // SRV.2.3.3.3 (search for "error dispatch") - if (servletResponse instanceof HttpServletResponse) { - ((HttpServletResponse) servletResponse).setStatus( - HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + if (fireOnError) { + AsyncEvent errorEvent = new AsyncEvent(event.getAsyncContext(), + event.getSuppliedRequest(), event.getSuppliedResponse(), t); + List<AsyncListenerWrapper> listenersCopy = + new ArrayList<AsyncListenerWrapper>(); + listenersCopy.addAll(listeners); + for (AsyncListenerWrapper listener : listenersCopy) { + try { + listener.fireOnError(errorEvent); + } catch (IOException ioe) { + log.warn("onError() failed for listener of type [" + + listener.getClass().getName() + "]", ioe); + } + } } - Host host = (Host) context.getParent(); - Valve stdHostValve = host.getPipeline().getBasic(); - if (stdHostValve instanceof StandardHostValve) { - ((StandardHostValve) stdHostValve).throwable(request, - request.getResponse(), t); - } - if (isStarted() && !request.isAsyncDispatching()) { - complete(); + AtomicBoolean result = new AtomicBoolean(); + request.getCoyoteRequest().action(ActionCode.ASYNC_IS_ERROR, result); + if (result.get()) { + // No listener called dispatch() or complete(). This is an error. + // SRV.2.3.3.3 (search for "error dispatch") + if (servletResponse instanceof HttpServletResponse) { + ((HttpServletResponse) servletResponse).setStatus( + HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } + + Host host = (Host) context.getParent(); + Valve stdHostValve = host.getPipeline().getBasic(); + if (stdHostValve instanceof StandardHostValve) { + ((StandardHostValve) stdHostValve).throwable(request, + request.getResponse(), t); + } + + request.getCoyoteRequest().action( + ActionCode.ASYNC_IS_ERROR, result); + if (result.get()) { + // Still in the error state. The error page did not call + // complete() or dispatch(). Complete the async processing. + complete(); + } } } Modified: tomcat/tc7.0.x/trunk/java/org/apache/coyote/ActionCode.java URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/coyote/ActionCode.java?rev=1409036&r1=1409035&r2=1409036&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/java/org/apache/coyote/ActionCode.java (original) +++ tomcat/tc7.0.x/trunk/java/org/apache/coyote/ActionCode.java Tue Nov 13 23:55:26 2012 @@ -192,6 +192,11 @@ public enum ActionCode { ASYNC_IS_TIMINGOUT, /** + * Callback to determine if async is in error + */ + ASYNC_IS_ERROR, + + /** * Callback to trigger the HTTP upgrade process. */ UPGRADE Modified: tomcat/tc7.0.x/trunk/java/org/apache/coyote/AsyncStateMachine.java URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/coyote/AsyncStateMachine.java?rev=1409036&r1=1409035&r2=1409036&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/java/org/apache/coyote/AsyncStateMachine.java (original) +++ tomcat/tc7.0.x/trunk/java/org/apache/coyote/AsyncStateMachine.java Tue Nov 13 23:55:26 2012 @@ -49,40 +49,46 @@ import org.apache.tomcat.util.res.String * DISPATCHING - The dispatch is being processed. * ERROR - Something went wrong. * - * |----------------->--------------| - * | \|/ - * | |----------<---------------ERROR - * | | complete() /|\ |postProcess() - * | | error()| | - * | | | | |--|timeout() - * | | postProcess() | \|/ | \|/ auto - * | | |--------------->DISPATCHED<------------------COMPLETING<----| - * | | | /|\ | | /|\ | - * | | | |--->-------| | |--| | - * | | ^ | |startAsync() timeout() | - * | | | | | | - * | \|/ | | complete() \|/ postProcess() | - * | MUST_COMPLETE-<- | ----<------STARTING-->----------------| ^ - * | /|\ | | | | - * | | | | | | - * | | ^ |dispatch() | | - * | | | | | | - * | | | \|/ \|/ complete() | - * | | | MUST_DISPATCH STARTED---->-------| - * | | | | | | - * | | | |postProcess() | | - * ^ ^ | | dispatch() | |auto - * | | | | |--------------------| | - * | | | auto \|/ \|/ \|/ - * | | |---<----DISPATCHING<-----------------TIMING_OUT - * | | dispatch() | | - * | | | | - * | |-------<-------------------------------------<----| | - * | complete() | - * | | - * |----<------------------------<-----------------------------<--| - * error() - * </pre> + * |----------------->--------------| + * | \|/ + * | |----------<---------------ERROR + * | | complete() /|\ | \ + * | | | | \---------------| + * | | | | |dispatch() + * | | | |postProcess() \|/ + * | | error()| | | + * | | | | |--|timeout() | + * | | postProcess() | \|/ | \|/ | auto + * | | |--------------->DISPATCHED<---------- | --------------COMPLETING<-----| + * | | | /|\ | | | /|\ | + * | | | |--->-------| | | |--| | + * | | ^ | |startAsync() | timeout() | + * | | | | | | | + * | \|/ | | complete() \|/ postProcess() | | + * | MUST_COMPLETE-<- | ----<------STARTING-->--------- | ------------| ^ + * | /|\ | | | | complete() | + * | | | | | | /-----------| + * | | ^ |dispatch() | | / + * | | | | | | / + * | | | \|/ / \|/ / + * | | | MUST_DISPATCH / STARTED + * | | | | / /| + * | | | |postProcess() / / | + * ^ ^ | | / dispatch()/ | + * | | | | / / | + * | | | | |---------- / -----------/ |auto + * | | | | | / | + * | | | | | |-----/ | + * | | | auto \|/ \|/ \|/ \|/ + * | | |---<------DISPATCHING<-----------------TIMING_OUT + * | | dispatch() | | + * | | | | + * | |-------<----------------------------------<------| | + * | complete() | + * | | + * |<--------<-------------------<-------------------------------<--| + * error() + * </pre> */ public class AsyncStateMachine<S> { @@ -155,6 +161,9 @@ public class AsyncStateMachine<S> { return state == AsyncState.TIMING_OUT; } + public boolean isAsyncError() { + return state == AsyncState.ERROR; + } public synchronized void asyncStart(AsyncContextCallback asyncCtxt) { if (state == AsyncState.DISPATCHED) { @@ -191,13 +200,6 @@ public class AsyncStateMachine<S> { } else if (state == AsyncState.DISPATCHING) { state = AsyncState.DISPATCHED; return SocketState.ASYNC_END; - } else if (state == AsyncState.ERROR) { - asyncCtxt.fireOnComplete(); - state = AsyncState.DISPATCHED; - return SocketState.ASYNC_END; - //} else if (state == AsyncState.DISPATCHED) { - // // No state change - // return SocketState.OPEN; } else { throw new IllegalStateException( sm.getString("asyncStateMachine.invalidAsyncState", Modified: tomcat/tc7.0.x/trunk/java/org/apache/coyote/ajp/AbstractAjpProcessor.java URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/coyote/ajp/AbstractAjpProcessor.java?rev=1409036&r1=1409035&r2=1409036&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/java/org/apache/coyote/ajp/AbstractAjpProcessor.java (original) +++ tomcat/tc7.0.x/trunk/java/org/apache/coyote/ajp/AbstractAjpProcessor.java Tue Nov 13 23:55:26 2012 @@ -457,6 +457,8 @@ public abstract class AbstractAjpProcess ((AtomicBoolean) param).set(asyncStateMachine.isAsync()); } else if (actionCode == ActionCode.ASYNC_IS_TIMINGOUT) { ((AtomicBoolean) param).set(asyncStateMachine.isAsyncTimingOut()); + } else if (actionCode == ActionCode.ASYNC_IS_ERROR) { + ((AtomicBoolean) param).set(asyncStateMachine.isAsyncError()); } else if (actionCode == ActionCode.UPGRADE) { // HTTP connections only. Unsupported for AJP. // NOOP Modified: tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/AbstractHttp11Processor.java URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/AbstractHttp11Processor.java?rev=1409036&r1=1409035&r2=1409036&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/AbstractHttp11Processor.java (original) +++ tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/AbstractHttp11Processor.java Tue Nov 13 23:55:26 2012 @@ -839,6 +839,8 @@ public abstract class AbstractHttp11Proc ((AtomicBoolean) param).set(asyncStateMachine.isAsync()); } else if (actionCode == ActionCode.ASYNC_IS_TIMINGOUT) { ((AtomicBoolean) param).set(asyncStateMachine.isAsyncTimingOut()); + } else if (actionCode == ActionCode.ASYNC_IS_ERROR) { + ((AtomicBoolean) param).set(asyncStateMachine.isAsyncError()); } else if (actionCode == ActionCode.UPGRADE) { upgradeInbound = (UpgradeInbound) param; // Stop further HTTP output Modified: tomcat/tc7.0.x/trunk/test/org/apache/catalina/core/TestAsyncContextImpl.java URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/test/org/apache/catalina/core/TestAsyncContextImpl.java?rev=1409036&r1=1409035&r2=1409036&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/test/org/apache/catalina/core/TestAsyncContextImpl.java (original) +++ tomcat/tc7.0.x/trunk/test/org/apache/catalina/core/TestAsyncContextImpl.java Tue Nov 13 23:55:26 2012 @@ -472,9 +472,7 @@ public class TestAsyncContextImpl extend } } else { expected.append("onTimeout-"); - if (dispatchUrl == null) { - expected.append("onError-"); - } else { + if (dispatchUrl != null) { expected.append("NonAsyncServletGet-"); } expected.append("onComplete-"); --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org