Fix https://issues.apache.org/bugzilla/show_bug.cgi?id=49698
authormarkt <markt@13f79535-47bb-0310-9956-ffa450edef68>
Sat, 28 Aug 2010 11:07:39 +0000 (11:07 +0000)
committermarkt <markt@13f79535-47bb-0310-9956-ffa450edef68>
Sat, 28 Aug 2010 11:07:39 +0000 (11:07 +0000)
Allow listeners to call complete when a async request times out
Add a test case based on pero's previous timeout test case

git-svn-id: https://svn.apache.org/repos/asf/tomcat/trunk@990342 13f79535-47bb-0310-9956-ffa450edef68

java/org/apache/catalina/core/AsyncContextImpl.java
test/org/apache/catalina/core/TestAsyncContextImpl.java
webapps/docs/changelog.xml

index a15ca27..955e77c 100644 (file)
@@ -51,7 +51,7 @@ public class AsyncContextImpl implements AsyncContext {
     
     public static enum AsyncState {
         NOT_STARTED, STARTED, DISPATCHING, DISPATCHED, COMPLETING, TIMING_OUT,
-        ERROR_DISPATCHING
+        TIMING_OUT_NEED_COMPLETE, ERROR_DISPATCHING
     }
     
     private static final Log log = LogFactory.getLog(AsyncContextImpl.class);
@@ -82,13 +82,19 @@ public class AsyncContextImpl implements AsyncContext {
         }
         if (state.get()==AsyncState.COMPLETING) {
             //do nothing
-        } else if (state.compareAndSet(AsyncState.DISPATCHED, AsyncState.COMPLETING) ||
-                   state.compareAndSet(AsyncState.STARTED, AsyncState.COMPLETING)) {
+        } else if (state.compareAndSet(AsyncState.DISPATCHED,
+                           AsyncState.COMPLETING) ||
+                   state.compareAndSet(AsyncState.STARTED,
+                           AsyncState.COMPLETING) ||
+                   state.compareAndSet(AsyncState.TIMING_OUT_NEED_COMPLETE,
+                           AsyncState.COMPLETING)) {
             AtomicBoolean dispatched = new AtomicBoolean(false);
-            request.getCoyoteRequest().action(ActionCode.ACTION_ASYNC_COMPLETE,dispatched);
+            request.getCoyoteRequest().action(ActionCode.ACTION_ASYNC_COMPLETE,
+                    dispatched);
             if (!dispatched.get()) doInternalComplete(false);
         } else {
-            throw new IllegalStateException("Complete not allowed. Invalid state:"+state.get());
+            throw new IllegalStateException(
+                    "Complete not allowed. Invalid state:"+state.get());
         }
        
     }
@@ -296,10 +302,14 @@ public class AsyncContextImpl implements AsyncContext {
     }
     
     public void doInternalDispatch() throws ServletException, IOException {
-        if (this.state.compareAndSet(AsyncState.TIMING_OUT, AsyncState.COMPLETING)) {
+        if (this.state.compareAndSet(AsyncState.TIMING_OUT,
+                AsyncState.TIMING_OUT_NEED_COMPLETE)) {
             log.debug("TIMING OUT!");
             boolean listenerInvoked = false;
-            for (AsyncListenerWrapper listener : listeners) {
+            List<AsyncListenerWrapper> listenersCopy =
+                new ArrayList<AsyncListenerWrapper>();
+            listenersCopy.addAll(listeners);
+            for (AsyncListenerWrapper listener : listenersCopy) {
                 listener.fireOnTimeout(event);
                 listenerInvoked = true;
             }
index cf049fa..e12b629 100644 (file)
 
 package org.apache.catalina.core;
 
+import java.io.File;
 import java.io.IOException;
 
 import javax.servlet.AsyncContext;
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
@@ -308,4 +311,70 @@ public class TestAsyncContextImpl extends TomcatBaseTest {
         }
     }
 
+    public void testTimeout() throws Exception {
+        // Setup Tomcat instance
+        Tomcat tomcat = getTomcatInstance();
+        
+        // Must have a real docBase - just use temp
+        File docBase = new File(System.getProperty("java.io.tmpdir"));
+        
+        // Create the folder that will trigger the redirect
+        File foo = new File(docBase, "async");
+        if (!foo.exists() && !foo.mkdirs()) {
+            fail("Unable to create async directory in docBase");
+        }
+        
+        Context ctx = tomcat.addContext("/", docBase.getAbsolutePath());
+
+        TimeoutServlet timeout = new TimeoutServlet();
+
+        Wrapper wrapper = Tomcat.addServlet(ctx, "time", timeout);
+        wrapper.setAsyncSupported(true);
+        ctx.addServletMapping("/async", "time");
+
+        tomcat.start();
+        ByteChunk res = getUrl("http://localhost:" + getPort() + "/async");
+        assertEquals("OK", res.toString());
+    }
+    
+    private static class TimeoutServlet extends HttpServlet {
+        private static final long serialVersionUID = 1L;
+
+        @Override
+        protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
+                throws ServletException, IOException {
+            if (req.isAsyncSupported()) {
+                resp.getWriter().print("OK");
+                final AsyncContext ac = req.startAsync();
+                ac.setTimeout(2000);
+                
+                ac.addListener(new TimeoutListener());
+            } else
+                resp.getWriter().print("FAIL: Async unsupported");
+        }
+    }
+
+    private static class TimeoutListener implements AsyncListener {
+
+        @Override
+        public void onComplete(AsyncEvent event) throws IOException {
+            // NO-OP
+        }
+
+        @Override
+        public void onTimeout(AsyncEvent event) throws IOException {
+            event.getAsyncContext().complete();
+        }
+
+        @Override
+        public void onError(AsyncEvent event) throws IOException {
+            // NOOP
+        }
+
+        @Override
+        public void onStartAsync(AsyncEvent event) throws IOException {
+            // NOOP
+        }
+        
+    }
 }
index 7b345f8..1aa146b 100644 (file)
         refactoring. (markt)
       </fix>
       <fix>
+        <bug>49698</bug>: Allow a listener to complete an asynchronous request
+        if it times out. (markt)
+      </fix>
+      <fix>
         <bug>49714</bug>: The annotation process of Jar doesn't influence 
         distributable element of web.xml. (kfujino)
       </fix>