google-app-enginechannel-api

GAE/Java LocalChannelFailureException at development server


I'm using Channel API (Java) with Google App Engine for my web application. I have implemented a Token-reusing-mechanism for not exceeding the Channel API Quotas that fast. This means, that my implementation reuses an existing channel for a user that refreshes the page as long as the expiration time of the token received by the ChannelService.createChannel() call, is not over.

When refreshing my page I get the following exception (with x starting at 0 and increasing for every refresh). However, my page continues to work as intended. Is there a way to avoid the exception being thrown? Or can I just ignore the exception?

com.google.appengine.api.channel.dev.LocalChannelFailureException: Client connection with ID connection-x not found.
at com.google.appengine.api.channel.dev.Channel.getClientMessageQueue(Channel.java:79)
at com.google.appengine.api.channel.dev.ChannelManager.getNextClientMessage(ChannelManager.java:300)
at com.google.appengine.api.channel.dev.LocalChannelServlet.doGet(LocalChannelServlet.java:120)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:617)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:511)
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1166)
...

Im reusing tokens with the following classes:

When calling ChannelService.createChannel() I save the expiration date and the generated token in an Entity called "Channel"

public class Channel {
    private String id;
    private String token;
    private Date expiration;
}

Then I have a ChannelService class that returns a valid Channel with its get() method. The channelDAO is a class that just uses a Map for storing Channels. So there is no database persistence, which would keep a token alive over a server restart.

public Channel get(String clientId) {
    Calendar calendar = Calendar.getInstance();
    Channel channel = channelDAO.get(clientId);
    if (channel == null || calendar.getTime().after(channel.getExpiration())) {
        com.google.appengine.api.channel.ChannelService channelService = ChannelServiceFactory
                .getChannelService();
        calendar.add(Calendar.MINUTE, CHANNEL_UPTIME);
        String token = channelService.createChannel(player.toString(), CHANNEL_UPTIME);
        channel = new Channel(clientId, token, calendar.getTime());
        channelDAO.persist(channel);
    }
    return channel;
}

Solution

  • I fixed the problem by further investigations on the source of the exception. The Channel API works with polling requests that are executed every 500ms. I used Firefox's console to track these. Here is an example poll:

    [20:40:15.978] GET http://localhost:8080/_ah/channel/dev?command=poll&channel=920a60f9b27ece1a1ba43d251fdacf2e-channel-eqt3xi-1385927324758-{clientId}&client=connection-2 [HTTP/1.1 200 OK 0ms]
    

    In my question I stated, that the exception occurs on page reload, so the problem with this was: When the page is reloaded, something (I don't know what exactly, but i assume it has something to do with sockets getting closed and reopened on page refresh) happens which causes the client (last parameter of the GET request) to no longer be available. However, a new client is available: the client "connection-{i+1}". So when you enter the page initially, the client is "connection-0". After page refresh it is "connection-1". But as the old page used a delayed execution for the poll, a false request (still connection-0) is sent to the server, that, as a result, throws the Exception.

    I fixed the problem by manually cancelling the delayed execution, when leaving the page with jQuery.

    var channel = new goog.appengine.Channel('${channel.token}');
    var socket = channel.open(handler);
    $(window).on('beforeunload', function() {
        clearTimeout(socket.pollingTimer_);
    });