As I have previously blogged, asynchronous coding is hard! The suspend proposal for Servlet 3.0 does take a lot of the pain out of asynchronous programming, but not all.  It has been pointed out, that my own async examples make some assumptions that simplify the code. Specifically they assume that there are no upstream suspenders (eg a filter deployed in front) that have already suspended and resumed and thus affected the values returned by isInitial, isResumed and isTimeout.
So these examples need to be a little more complex to deal with all circumstances. One way to deal with such complexity is with patterns, which can help explain the generic cases, provide a template for specific implementations and/or be the basis of frameworks to help developers.   Thus I have captured the key usages of the suspend API in the following 5 patterns:

Suspend/Complete Servlet

This is the simplest pattern, where a servlet suspends a request and organizes for the response to be completed by asynchronous threads or call backs. This is not affected by any upstream suspenders

  public void doGet(HttpServletRequest request,                     HttpServletResponse response)  {    request.suspend();    // arrange for response to be completed    // by async thread(s) or callback(s)  }

Simple Suspend/Resume Servlet

If a servlet developer knows that the servlet will not be fronted by suspending filters, then it can use a simplified pattern:

  public void doGet(HttpServletRequest request,                     HttpServletResponse response)  {    if(request.isInitial())    {      // handle intial dispatch      request.suspend();      // arrange async thread/callback      return;    }
    if(request.isTimeout())    {      // handle timeout    }
    // generate response  }

Note that the suspend call should happen before arranging async thread/callback so that there is not a risk of a resume before the suspend.

Suspend/Resume Servlet

If a suspending servlet can be downstream of a filter (or dispatching servlet) that also suspends, then the isInitial(), isTimeout() and isResumed() methods may not be set due to this servlets suspend. A request attribute is required to flag that this servlet has performed the suspend. The attribute name needs to be chosen so that it will not clash with other instances. The attribute value may be a simple boolean or a more complex state object to pass information from the initial to he resume/timeout handling.

  public void doGet(HttpServletRequest request,                     HttpServletResponse response)  {    if(request.isInitial()     || request.getAttribute("com.acme.suspend")==null)    {      // handle intial dispatch      request.setAttribute("com.acme.suspend",                           Boolean.TRUE);      request.suspend();      // arrange callback      return;    }
    Boolean suspended=request.getAttribute("com.acme.suspend");    if (suspended)    {      request.setAttribute("com.acme.suspend",Boolean.FALSE);
      if(request.isTimeout())      {        // handle timeout        return;      }
      // handle resume    }
    // generate response  }

The isInitial() call is still used as an efficiency.  If it is is true, then the request is initial for all filters and servlets.  The value of the attribute only needs to be checked if initial returns false.

Simple Suspend/Resume Filter

If a filter developer knows that there are no upstream or downstream suspenders, then a simplified pattern similar to the Simpler Suspend/Resume Servlet may be used:

  public void doFilter(ServletRequest request,                        ServletResponse response,                        FilterChain chain)  {
    if(request.isInitial())    {      // handle intial dispatch      request.suspend();      // arrange async callback      return;    }
    if(request.isTimeout())    {      // handle timeout      return;    }    // handle resume    chain.doFilter(request,response);  }

Suspend/Resume Filter

If a suspending filter is to be deployed where there may be either/both upstream and/or downstream suspending components, then a request attribute needs to be used to track both the initial handling and to signal that the resume/timeout has been handled. The attribute value may be a simple boolean or a more complex state object to pass information from the initial to the resume/timeout handling.

  public void doFilter(ServletRequest request,                        ServletResponse response,                        FilterChain chain)  {
    if(request.isInitial() || request.getAttribute("com.acme.suspend")==null)    {      // handle intial dispatch      request.setAttribute("com.acme.suspend",Boolean.TRUE);      request.suspend();      // arrange async callback      return;    }
    Boolean suspended=request.getAttribute("com.acme.suspend");    if (suspended)    {      request.setAttribute("com.acme.suspend",Boolean.FALSE);
      if(request.isTimeout())      {        // handle timeout        return;      }
      // handle resume    }
    chain.doFilter(request,response);  }

5 Comments

Jim Morris · 18/08/2008 at 20:30

I had a heck of a time avoiding race conditions using the continuations mechanism, and although I use Continuations everywhere, I look forward to deploying with 7.0’s new suspend/resume API. It’ll be a lot of work to upgrade but I think it will simplify the code, especially the race where the response could return before the thread was suspended, which took a lot of hacking to work around with continuations, once I actually noticed it was a problem.

Jean-Francois Arcand's Blog · 14/10/2008 at 21:39

[Trackback] Introducing Atmosphere, a new framework for building portable Comet based applications. Yes, portable, which means it can run on Tomcat, Jetty, GlassFish or any web server that support Servlet 2.5 … and without the needs to learn all those private AP…

Nick Kallen · 02/03/2009 at 06:25

I’ve been playing with the proposed Async API. It’s simple in isolation but complex in combination with other filters.
I have a cache proxy filter. It calls the chain on cache miss. The underlying filter is async. Because of the suspending downstream, the filter might try to store the response before the response is computed. When the response is resumed, the filter gets invoked a second time, so we do a second cache lookup, we get a hit this time (because stored an incomplete response earlier!)
So I’m not sure I like the API. If a filter cannot continue to work with a servlet as if it were not async, you’ve broken closure and encapsulation. That’s pretty bad IMO.

Greg Wilkins · 02/03/2009 at 08:15

Nick,
indeed some types of filters will have to be carefully (re)written to deal with async. But the good news for this, is that in the current proposed API, there are more methods to help a filter know if a request has been “suspended” and if a dispatch is a resume.
A caching filter is indeed one that would need to be aware of empty suspended requests.

Florian · 15/06/2009 at 20:49

Hi Greg,
I’m thinking of using Jetty as a server for an online multi-player game. My client is a flash application, and in the setup I have so far, it just connects (via Flash’s XMLSocket) to another port on my server where a Java ServerSocket is listening. The connection is then kept open as long as the person is logged in. As I understand in comet-style setups this is different, i.e. they regularly close the socket and immediately reopen it.
Is it possible to use Jetty for the use case I have in mind? I’d really love to have Jetty serve all my html/flash files and at the same time use it (preferably on port 80 as well) for handling the game communication (with a socket that is continuosly kept open).
Best regards,
Florian

Comments are closed.