public class AsyncContextImpl implements AsyncContext {
public static enum AsyncState {
- NOT_STARTED, STARTED, DISPATCHING, DISPATCHING_RUNNABLE, DISPATCHED,
- COMPLETING, COMPLETING_RUNNABLE, TIMING_OUT, ERROR_DISPATCHING
+ NOT_STARTED, STARTED, DISPATCHING, DISPATCHED, COMPLETING, TIMING_OUT,
+ ERROR_DISPATCHING
}
private static final Log log = LogFactory.getLog(AsyncContextImpl.class);
AtomicBoolean dispatched = new AtomicBoolean(false);
request.getCoyoteRequest().action(ActionCode.ACTION_ASYNC_COMPLETE,dispatched);
if (!dispatched.get()) doInternalComplete(false);
- } else if (state.compareAndSet(AsyncState.DISPATCHING_RUNNABLE,
- AsyncState.COMPLETING_RUNNABLE)) {
- // do nothing
} else {
throw new IllegalStateException("Complete not allowed. Invalid state:"+state.get());
}
log.debug("AsyncContext Start Called["+state.get()+"; "+request.getRequestURI()+"?"+request.getQueryString()+"]", new DebugException());
}
- if (state.compareAndSet(AsyncState.STARTED, AsyncState.DISPATCHING_RUNNABLE) ||
- state.compareAndSet(AsyncState.DISPATCHED, AsyncState.DISPATCHING_RUNNABLE)) {
- // TODO SERVLET3 - async
- final ServletContext sctx = getServletRequest().getServletContext();
- Runnable r = new Runnable() {
- public void run() {
- //TODO SERVLET3 - async - set context class loader when running the task.
- try {
-
- run.run();
- }catch (Exception x) {
- log.error("Unable to run async task.",x);
- }
- }
- };
- this.dispatch = r;
- AtomicBoolean dispatched = new AtomicBoolean(false);
- request.getCoyoteRequest().action(ActionCode.ACTION_ASYNC_DISPATCH, dispatched );
- if (!dispatched.get()) {
- try {
- doInternalDispatch();
- }catch (ServletException sx) {
- throw new RuntimeException(sx);
- }catch (IOException ix) {
- throw new RuntimeException(ix);
- }
- }
+ if (state.get() == AsyncState.STARTED) {
+ // TODO SERVLET3 - async - set context class loader when running the
+ // task.
+ // final ServletContext sctx = getServletRequest().getServletContext();
+ // TODO - Use a container thread without creating a memory leak
+ // Execute the runnable using a container thread from the
+ // Connector's thread pool
+ // request.getConnector().getProtocolHandler().getExecutor().execute(run);
+ Thread t = new Thread(run);
+ t.start();
} else {
throw new IllegalStateException("Dispatch not allowed. Invalid state:"+state.get());
}
public boolean isStarted() {
return (state.get() == AsyncState.STARTED ||
- state.get() == AsyncState.DISPATCHING ||
- state.get() == AsyncState.DISPATCHING_RUNNABLE);
+ state.get() == AsyncState.DISPATCHING);
}
public void setStarted(Context context) {
listener.fireOnTimeout(event);
listenerInvoked = true;
}
- if (!listenerInvoked) {
- ((HttpServletResponse)servletResponse).setStatus(500);
+ if (listenerInvoked) {
+ // Listener should have called complete
+ if (state.get() != AsyncState.NOT_STARTED) {
+ ((HttpServletResponse)servletResponse).setStatus(500);
+ doInternalComplete(true);
+ }
+ } else {
+ // No listeners, container calls complete
+ doInternalComplete(false);
}
- doInternalComplete(true);
} else if (this.state.compareAndSet(AsyncState.ERROR_DISPATCHING, AsyncState.COMPLETING)) {
log.debug("ON ERROR!");
boolean listenerInvoked = false;
dispatch = null;
}
}
- } else if (this.state.get() == AsyncState.DISPATCHING_RUNNABLE) {
- if (this.dispatch!=null) {
- try {
- dispatch.run();
- } catch (RuntimeException x) {
- doInternalComplete(true);
- if (x.getCause() instanceof ServletException) throw (ServletException)x.getCause();
- if (x.getCause() instanceof IOException) throw (IOException)x.getCause();
- else throw new ServletException(x);
- } finally {
- dispatch = null;
- }
- if (this.state.compareAndSet(AsyncState.COMPLETING_RUNNABLE,
- AsyncState.COMPLETING)) {
- doInternalComplete(false);
- } else if (this.state.get() == AsyncState.DISPATCHING_RUNNABLE) {
- doInternalComplete(true);
- throw new IllegalStateException(
- "Failed to call dispatch() or complete() after start()");
- }
- }
} else if (this.state.get()==AsyncState.COMPLETING) {
doInternalComplete(false);
} else {
import java.io.IOException;
+import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import org.apache.catalina.Wrapper;
import org.apache.catalina.startup.Tomcat;
import org.apache.catalina.startup.TomcatBaseTest;
+import org.apache.tomcat.util.buf.ByteChunk;
public class TestAsyncContextImpl extends TomcatBaseTest {
tomcat.start();
// Call the servlet once
- getUrl("http://localhost:" + getPort() + "/");
+ ByteChunk bc = getUrl("http://localhost:" + getPort() + "/");
+ assertEquals("OK", bc.toString());
+
+
+ assertEquals("1false2true3true4true5false", servlet.getResult());
+ }
+
+ public void testBug49567() throws Exception {
+ // Setup Tomcat instance
+ Tomcat tomcat = getTomcatInstance();
+
+ // Must have a real docBase - just use temp
+ Context ctx =
+ tomcat.addContext("/", System.getProperty("java.io.tmpdir"));
+
+ Bug49567Servlet servlet = new Bug49567Servlet();
+
+ Wrapper wrapper = Tomcat.addServlet(ctx, "servlet", servlet);
+ wrapper.setAsyncSupported(true);
+ ctx.addServletMapping("/", "servlet");
+
+ tomcat.start();
+
+ // Call the servlet once
+ ByteChunk bc = getUrl("http://localhost:" + getPort() + "/");
+ assertEquals("OK", bc.toString());
assertEquals("1false2true3true4true5false", servlet.getResult());
}
+ public void testAsyncStartNoComplete() throws Exception {
+ // Setup Tomcat instance
+ Tomcat tomcat = getTomcatInstance();
+
+ // Minimise pauses during test
+ tomcat.getConnector().setAttribute(
+ "connectionTimeout", Integer.valueOf(3000));
+
+ // Must have a real docBase - just use temp
+ Context ctx =
+ tomcat.addContext("/", System.getProperty("java.io.tmpdir"));
+
+ AsyncStartNoCompleteServlet servlet =
+ new AsyncStartNoCompleteServlet();
+
+ Wrapper wrapper = Tomcat.addServlet(ctx, "servlet", servlet);
+ wrapper.setAsyncSupported(true);
+ ctx.addServletMapping("/", "servlet");
+
+ tomcat.start();
+
+ // Call the servlet the first time
+ ByteChunk bc1 = getUrl("http://localhost:" + getPort() +
+ "/?echo=run1");
+ assertEquals("OK-run1", bc1.toString());
+
+ // Call the servlet the second time with a request parameter
+ ByteChunk bc2 = getUrl("http://localhost:" + getPort() +
+ "/?echo=run2");
+ assertEquals("OK-run2", bc2.toString());
+ }
+
+ public void testAsyncStartWithComplete() throws Exception {
+ // Setup Tomcat instance
+ Tomcat tomcat = getTomcatInstance();
+
+ // Must have a real docBase - just use temp
+ Context ctx =
+ tomcat.addContext("/", System.getProperty("java.io.tmpdir"));
+
+ AsyncStartWithCompleteServlet servlet =
+ new AsyncStartWithCompleteServlet();
+
+ Wrapper wrapper = Tomcat.addServlet(ctx, "servlet", servlet);
+ wrapper.setAsyncSupported(true);
+ ctx.addServletMapping("/", "servlet");
+
+ tomcat.start();
+
+ // Call the servlet once
+ ByteChunk bc = getUrl("http://localhost:" + getPort() + "/");
+ assertEquals("OK", bc.toString());
+ }
+
private static class Bug49528Servlet extends HttpServlet {
private static final long serialVersionUID = 1L;
Thread.sleep(1000);
result.append('4');
result.append(req.isAsyncStarted());
+ resp.setContentType("text/plain");
+ resp.getWriter().print("OK");
req.getAsyncContext().complete();
result.append('5');
result.append(req.isAsyncStarted());
} catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
+ result.append(e);
+ } catch (IOException e) {
+ result.append(e);
}
}
});
req.getMethod();
}
}
+
+ private static class Bug49567Servlet extends HttpServlet {
+
+ private static final long serialVersionUID = 1L;
+
+ private StringBuilder result = new StringBuilder();
+
+ public String getResult() {
+ return result.toString();
+ }
+
+ @Override
+ protected void doGet(final HttpServletRequest req,
+ final HttpServletResponse resp)
+ throws ServletException, IOException {
+
+ result.append('1');
+ result.append(req.isAsyncStarted());
+ req.startAsync();
+ result.append('2');
+ result.append(req.isAsyncStarted());
+
+ req.getAsyncContext().start(new Runnable() {
+ @Override
+ public void run() {
+ Thread t = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ result.append('3');
+ result.append(req.isAsyncStarted());
+ Thread.sleep(1000);
+ result.append('4');
+ result.append(req.isAsyncStarted());
+ resp.setContentType("text/plain");
+ resp.getWriter().print("OK");
+ req.getAsyncContext().complete();
+ result.append('5');
+ result.append(req.isAsyncStarted());
+ } catch (InterruptedException e) {
+ result.append(e);
+ } catch (IOException e) {
+ result.append(e);
+ }
+ }
+ });
+ t.start();
+ }
+ });
+ // Pointless method call so there is somewhere to put a break point
+ // when debugging
+ req.getMethod();
+ }
+ }
+
+ private static class AsyncStartNoCompleteServlet extends HttpServlet {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected void doGet(final HttpServletRequest req,
+ final HttpServletResponse resp)
+ throws ServletException, IOException {
+
+ String echo = req.getParameter("echo");
+ AsyncContext actxt = req.startAsync();
+ resp.setContentType("text/plain");
+ resp.getWriter().print("OK");
+ if (echo != null) {
+ resp.getWriter().print("-" + echo);
+ }
+ // Speed up the test by reducing the timeout
+ actxt.setTimeout(1000);
+ }
+ }
+
+ private static class AsyncStartWithCompleteServlet extends HttpServlet {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected void doGet(final HttpServletRequest req,
+ final HttpServletResponse resp)
+ throws ServletException, IOException {
+
+ AsyncContext actxt = req.startAsync();
+ resp.setContentType("text/plain");
+ resp.getWriter().print("OK");
+ actxt.complete();
+ }
+ }
+
}