Skip to main content

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [List Home]
[jetty-dev] Unintuitive behavior of async context

Consider the following servlet emulating long-running output (>30s) via Async context:

==============================================================================
public class AsyncCtxSnippet extends HttpServlet {
    private static int workerCount = 0;

    private static class Worker implements Runnable {
        private AsyncContext ctx;

        public Worker(AsyncContext ctx) {
            this.ctx = ctx;
            System.out.println("Creating worker " + (++workerCount));
        }

        @Override
        public void run() {
            try {
                ServletOutputStream out =
                    ctx.getResponse().getOutputStream();
                for (int i = 0; i < 40; i++) {
                    out.println("async " + i);
                    out.flush();
                    Thread.sleep(1000L);
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                ctx.complete();
            }
        }
    }

    protected void doGet(
        HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException
    {
        if (request.isAsyncStarted()) {
            System.out.println("Already processing this - skip?");
        } else {
            System.out.println("Processing new request:" + request);
            AsyncContext ctx = request.startAsync();
            //ctx.setTimeout(0);
            ctx.start(new Worker(ctx));
        }
    }

    @Override
    public void destroy() {
    }

    public static void main(String args[]) throws Exception {
        Server server = new Server(8080);

        ServletContextHandler context = new ServletContextHandler(
            server, "/", ServletContextHandler.SESSIONS);
        context.addServlet(AsyncCtxSnippet.class, "/async");

        server.start();
        server.join();
    }
}
==============================================================================

If I forget to set AsyncContext timeout (commented in the code) the following will happen:

* Container will reenter doGet with the same request and response objects with the isAsyncStarted() method returning false:

Processing new request:(GET /async)@18093747 org.eclipse.jetty.server.Request@11416b3
Using async context:org.eclipse.jetty.server.AsyncContextState@a456bb
Creating worker 1
Processing new request:(GET /async)@18093747 org.eclipse.jetty.server.Request@11416b3
Using async context:org.eclipse.jetty.server.AsyncContextState@a456bb
Creating worker 2

* On the client side both workers would write to the same stream:
...
async 30
async 0
async 31
async 1
async 32
async 2
async 33
async 3
async 34
async 4
...

until the first worker finishes and completes the context (which both workers share) with the following exception.

I guess this timeout behavior is not specified in the Servlet API and container is valid to do whatever it wants, but I think it breaks the principle of least astonishment. I would maybe expect "closed" context exception for the first (time-outed) worker with separate contexts, or some other notification that something went wrong (and the AsyncContext got time-outed), or maybe container would just give-up and not retry the request at all. What do you think?

Where in documentation of Jetty this retry policy is specified?

Is the only way that I can find out about timeout is AsyncContext listener? I haven't check but since the AsyncContext instance is the same in both workers I might receive notification in both workers...

So novice programmers (which I am) writing async servlets with long-running output would certainly face some unexpected behavior from the Jetty container here. Any help from the container that programmer is doing smth wrong would be priceless (maybe even warning in the log about timeout and reentering servlet is enough?).

Regards,
Alexandr Ekushev


Back to the top