Enterprise Java

Long Polling with Spring 3.2’s DeferredResult

In our last episode, the CEO of Agile Cowboys Inc had just hired a Java/Spring consultant by giving him the Porsche that he originally bought for his girlfriend. Being upset by the loss of her prize Porsche, the CEO’s girlfriend has told his wife of their affair. His wife, after cutting up the CEO’s suites has filed for divorce. Meanwhile the CEO has implemented a new ‘casual’ dress code at the office and the Java/Spring consultant has just arrived back from a spin in his new Porsche and is sitting down at his desk about to fix the TV company’s software… If this doesn’t mean anything to you then take a look at Long Polling Tomcat With Spring.

The Java/Spring Consultant has to fix the TV Company’s server resource problem before the next big game, and he knows he can do this by implementing Spring’s Deferred Result technique using the Servlet 3 specification as implemented on Tomcat 71

The first thing that the Java/Spring consultant does is to check the project’s pom.xml file. For an asynchronous Servlet 3 project, you must include the following dependency:

<dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.0.1</version>
        <scope>provided</scope>
    </dependency>

Next you must tell Tomcat that the Spring DispatcherServlet supports Servlet 3 asynchronous communications. This is achieved by adding the following line to your web.xml:

<async-supported>true</async-supported>

The complete DispatcherServlet configuration is:

<servlet>
 <servlet-name>appServlet</servlet-name>
 <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
 <init-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
 </init-param>
 <load-on-startup>1</load-on-startup>
        <async-supported>true</async-supported>
    </servlet>

Having sorted out the project configuration the Java/Spring Consultant swiftly moves on to the controller code. He replaces the Graduate Trainee’s SimpleMatchUpdateController with a new DeferredMatchUpdateController:

@Controller() 
public class DeferredMatchUpdateController { 

  @Autowired 
  private DeferredResultService updateService; 

  @RequestMapping(value = "/matchupdate/begin" + "", method = RequestMethod.GET) 
  @ResponseBody 
  public String start() { 
    updateService.subscribe(); 
    return "OK"; 
  } 

  @RequestMapping("/matchupdate/deferred") 
  @ResponseBody 
  public DeferredResult<Message> getUpdate() { 

    final DeferredResult<Message> result = new DeferredResult<Message>(); 
    updateService.getUpdate(result); 
    return result; 
  } 
}

The new DeferredMatchUpdateController is fairly simple. Like the SimpleMatchUpdateController it contains two methods: start() and getUpdate(), which do exactly the same job as their simple counterparts. This makes this controller a plugin replacement for the SimpleMatchUpdateController. The big difference is that the getUpdate() methods creates an instance of Spring’s DeferredResult, which it passes to the new DeferredResultService before returning it to Spring. Spring then parks the HTTP request allowing it to hang until the DeferredResult object has some data to return to the browser.

@Service("DeferredService") 
public class DeferredResultService implements Runnable { 

  private static final Logger logger = LoggerFactory.getLogger(DeferredResultService.class); 

  private final BlockingQueue<DeferredResult<Message>> resultQueue = new LinkedBlockingQueue<>(); 

  private Thread thread; 

  private volatile boolean start = true; 

  @Autowired 
  @Qualifier("theQueue") 
  private LinkedBlockingQueue<Message> queue; 

  @Autowired 
  @Qualifier("BillSkyes") 
  private MatchReporter matchReporter; 

  public void subscribe() { 
    logger.info("Starting server"); 
    matchReporter.start(); 
    startThread(); 
  } 

  private void startThread() { 

    if (start) { 
      synchronized (this) { 
        if (start) { 
          start = false; 
          thread = new Thread(this, "Studio Teletype"); 
          thread.start(); 
        } 
      } 
    } 
  } 

  @Override 
  public void run() { 

    while (true) { 
      try { 

        DeferredResult<Message> result = resultQueue.take(); 
        Message message = queue.take(); 

        result.setResult(message); 

      } catch (InterruptedException e) { 
        throw new UpdateException("Cannot get latest update. " + e.getMessage(), e); 
      } 
    } 
  } 

  public void getUpdate(DeferredResult<Message> result) { 
    resultQueue.add(result); 
  } 

}

Again, like its counterpart SimpleMatchUpdateService the DeferredResultService contains two methods:subscribe() and getUpdate()

Dealing with getUpdate(...), all it does it to add the newly created DeferredResult object to a LinkedBlockingQueue called resultQueue, so that it can be dealt with later when a match update is available.

The real work is done by the subscribe() method. First, this method starts the matchReporter, which feeds match updates into the autowired queue instance at the appropriate moment. It then calls the private startThread() method to start a worker thread. This is only started once and uses double check locking to ensure that this is done efficiently and without problems.

The thread’s run() method infinitely loops firstly taking a DeferredResult object from the resultQueue, if available, and then a Message object, representing a match update from the update queue, again if available. It then calls DeferredResult’s setResult(...) using the message object as the argument. Spring will now take over and the original long poll request will be completed and the data belatedly returned to the browser.

Note that in this sample code the run() method contains a while(true) loop. Whilst this technique simplifies the sample code, it’s not such a good idea when it comes to production code. One of the problems of using wayward, uncontrolled threads is that they stop Tomcat shutting down correctly and you usually have to use the good ol’ Unix kill command to stop your server. In production code it’s a good idea to include code to close worker threads like this down gracefully.

After a hard couple of hours work, the Java/Spring Consultant promotes his code to live, picks up the keys to the Porsche and takes off for a spin. The next Saturday, using Spring’s DeferredResult, the servers cope wonderfully: the users are happy, the President of the TV company is happy and the CEO of Agile Cowboys Inc is happy, although he has a nagging suspicion that he’s paid the consultant too much, but hey, it’s only money.

1When writing this blog I used Tomcat version 7.0.42

The code that accompanies this blog is available on Github at: https://github.com/roghughe/captaindebug/tree/master/long-poll
 

Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

2 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Craig
Craig
11 years ago

I prefer WebSockets to DeferredResult because they reduce network traffic and latency. The only cases where I might prefer DeferredResult are if I couldn’t use HTTPS (WebSockets don’t play nicely on browsers within restricted networks over regular HTTP) or I had a very low traffic site.

Marco
Marco
11 years ago

Even though it works in this case, double-check locking is really an anti-pattern and should be avoided. AtomicBoolean.compareAndSet would work better here.

Back to top button