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 awhile(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’ Unixkill
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
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.
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.