The Google Widget Toolkit allows Ajax applications to be developed in java code using the traditional UI widget paradigm. The toolkit includes support for RPC, but not for comet style Ajax push.
Online Gaming is an excellent use-case for Ajax and I’ve been working with Ryan Dewsbury of www.aplayr.com to convert his GWT powered games of risk and poker to use Jetty continuations.
First… play a lot of poker!
Then the main challenge is to convert the getEvents() RPC call that the aplayr games make for their Comet Ajax Push aspects. The getEvents() is a long poll, in that it waits until there is an event be returned before generating a response.
This had been implemented with a wait/notify, which works fine but has scalability issues as each player waiting for an event has an outstanding getEvents() request that is consuming a thread and associated resources. It was not possible to have more simultaneous players then there were threads in the thread pool. This is exactly the problem that Jetty continuations have been designed to solve.
Unfortunately GWT has not made it easy to use continuations within their RPC mechanism. Firstly they catch Throwable, so he Jetty RetryException is caught. Secondly they have made most of the methods on the GWT servlet final, so you cannot fix this by extension.
Luckily GWT is open source under the apache 2.0 license, so it was possible to do a cut/paste/edit job to fix this. The
OpenRemoteServiceServlet recently added to Jetty is version of GWTs RemoteServiceServlet without the final methods and a protected method for extending exception handling. We are lobbying Google to make this part of the next release.
Once the GWT remote service servlet has been opened up, it is trivial to extend it to support Continuations, which has been done in AsyncRemoteServiceServlet.
Because GWT RPC uses POSTS, the body of the request is consumed when the request is first handled and is not available when the request is retried. To handle this, the parsed contents of the POST are stored as a request attribute so they are available to retried requests without reparsing:

protected String readPayloadAsUtf8(HttpServletRequest request)
throws IOException, ServletException
{
String payload=(String)request.getAttribute(PAYLOAD);
if (payload==null)
{
payload=super.readPayloadAsUtf8(request);
request.setAttribute(PAYLOAD,payload);
}
return payload;
}

 
The exception handling is also extended to allow the continuation RetryException to propagate to the container. This has been done without any hard dependencies on Jetty code:

protected void handleException( String responsePayload,
Throwable caught )
{
throwIfRetyRequest(caught);
super.handleException( responsePayload, caught );
}
protected void throwIfRetyRequest( Throwable caught )
{
if (caught instanceof RuntimeException &&
"org.mortbay.jetty.RetryRequest"
.equals(caught.getClass().getName()))
{
throw (RuntimeException) caught;
}
}

 
With these extensions, the AsyncRemoteServiceServlet allows
any GWT RCP method to use continuations to suspend/resume processing. For example below is the Table class used by gpokr where a continuation is used to wait for an event to be available for a player.

class Table
{
Set waiters = new HashSet();
public Events getEvents( Context c )
{
Player p = getPlayer( c );
// if the player has no events.
if( p.events.size() == 0 )
{
synchronized( this )
{
// suspend the continuation waiting for events
Continuation continuation =
ContinuationSupport.getContinuation
(c.getRequest(),this);
waiters.add(continuation);
continuation.suspend(30000);
}
}
catch (InterruptedException e)
{
log(e);
}
}
return p.events;
}
protected void addEvent( Event e )
{
// give the event to all players
Iterator it = players.values().iterator();
while( it.hasNext() )
{
Player p = (Player)it.next();
player.events.add( event );
}
// resume continuations waiting for events
synchronized( this )
{
Iterator iter = waiters.interator();
while (iter.hasNext())
((Continuation)iter.next()).resume();
waiters.clear();
}
}

With the same style of event loop, the kdice game has been able to run over 200 players and many spectators with only a 100 threads!
And most importantly, you must remember never to go in with just pocket jacks and a full table. What was I thinking?


7 Comments

Anonymous · 26/02/2007 at 11:48

Hi there,

great article!! And fun to read… 🙂

Just one question: is there any full working example of how to use GWT/continuation? (Eg it isn’t clear how the GWT client side is supposed to work)

Thanks again for this contribution.

Benjamin · 08/03/2007 at 22:41

Agreed with the previous poster: do you have an example of what the server looks like? How does it get a handle of the client?

Anonymous · 15/03/2007 at 08:14

Hi there,

getting back to this wonderful blog to see if there was already some answer concerning a working example for the GWT/Continuation example?

It would be great Greg if you could provide us with some links.. 🙂

Thanks for your contribution. 🙂

L_K · 28/03/2007 at 08:14

Great article! I’m also very interested in a working example… I’m currently trying myself, but the problem is getting the client-side to call the servlet. Thank you in advance for any help!

Rob Jellinghaus · 16/04/2007 at 03:31

GWT 1.4 contains a patch that opens up the RemoteServiceServlet quite extensively.  Check it out, it should let you reimplement this continuation support with much less duplicated code.

Cameron Taggart · 02/08/2007 at 22:33

According to JIRA issue JETTY-399, Craig Day updated to work with 1.4.  http://jira.codehaus.org/browse/JETTY-399

mo rock · 29/05/2008 at 16:42

Hey I’ve started working on a GWT Comet Grizzly implementation. It’s pretty rough right now. Check it out  <a href="http://www.javascriptr.com/2008/05/28/gwt-grizzly-comet/">here</a&gt;

Comments are closed.