Search code examples
javagoogle-cloud-messagingsmack

Smack 4.1.0 GCM CCS stops responding after a while


Have implemented gcm ccs for chat module and i am able to send and receive messages. Below is the main connection module,

        config = XMPPTCPConnectionConfiguration.builder()
                .setServiceName("gcm-pesu.googleapis.com")
                .setPort(GCM_PORT)
                .setHost(GCM_SERVER)
                .setCompressionEnabled(false)
                .setConnectTimeout(30000)
                .setSecurityMode(SecurityMode.ifpossible)
                .setSendPresence(false)
                .setSocketFactory(SSLSocketFactory.getDefault())
                .build();

        connection = new XMPPTCPConnection(config);
        connection.connect();

        Roster roster = Roster.getInstanceFor(connection);
        roster.setRosterLoadedAtLogin(false);

        connection.addConnectionListener(new LoggingConnectionListener());

        // Handle incoming packets
        connection.addAsyncStanzaListener(new MyStanzaListener(), new MyStanzaFilter());

        // Log all outgoing packets
        connection.addPacketInterceptor(new MyStanzaInterceptor(), new MyStanzaFilter());

        connection.login(mProjectId + "@gcm.googleapis.com", mApiKey);
        logger.info("logged in: " + mProjectId);

        PingManager pm = PingManager.getInstanceFor(connection);
        pm.setPingInterval(300);
        pm.pingMyServer();
        pm.registerPingFailedListener(new PingFailedListener() {
            @Override
            public void pingFailed() {
                connection.disconnect();
                logger.error("GCM CCS, Ping failed !!");
            }
        });

The problem i am running into is not receiving any message from GCM, sent by client device after a while. Though, the heartbeat looks normal and i do get pong from GCM even in that case. Is it something to do with SSL ?

Have handled connection draining case as follows,

        String controlType = (String) jsonObject.get("control_type");
        volatile boolean connectionDraining = false;
        if ("CONNECTION_DRAINING".equals(controlType)) {
            connectionDraining = true;
            try {
                connection.disconnect();
                connect();
                connectionDraining = false;
            } catch (Exception e) {
                logger.error("Error establishing new connection after draining ", e);
            }
        }

Solution

  • Implemented queue of channels when one of it is draining.

                private Deque<Channel> channels;
                protected void handleControlMessage(Map<String, Object> jsonObject) {
                logger.info("Control message : " + jsonObject);
                String controlType = (String) jsonObject.get("control_type");
                if ("CONNECTION_DRAINING".equals(controlType)) {
                    connectionDraining = true;
                } 
            }
    

    Create new channel while sending message

            public void sendDownstreamMessage(String jsonRequest) {
            Channel channel = channels.peekFirst();
            try {
                if (channel.connectionDraining) {
                    synchronized (channels) {
                        channel = channels.peekFirst();
                        if (channel.connectionDraining) {
                            channels.addFirst(connect());
                            channel = channels.peekFirst();
                        }
                    }
                }
                channel.send(jsonRequest);
            } catch (Exception e) {
                logger.error("Message not sent. Error in connecting :", e);
            }
        }
    

    GCM will take care of closing the other. This resolved the issue.