Clustering plugin, error during login because of null presence

Hi there guys,

I’m new hear, I hope that you understand my question.

the scenario is that I have to 2 open fire nodes (clustering), and i have few clients connected to node 1, now if I stop node 1 then the clients try to reconnect to node 2, and for one of the clients I get this error (happens also for different clients not specific one):

2017-01-20 13:54:39,738 [httpbind-worker-5] ERROR org.jivesoftware.openfire.handler.IQBindHandler - Error during login

java.lang.NullPointerException

at org.jivesoftware.openfire.SessionManager.removeSession(SessionManager.java:1102 )

at com.jivesoftware.openfire.session.RemoteSession.doSynchronousClusterTask(Remote Session.java:175)

at com.jivesoftware.openfire.session.RemoteClientSession.incrementConflictCount(Re moteClientSession.java:149)

at org.jivesoftware.openfire.handler.IQBindHandler.handleIQ(IQBindHandler.java:134 )

at org.jivesoftware.openfire.handler.IQHandler.process(IQHandler.java:65)

at org.jivesoftware.openfire.IQRouter.handle(IQRouter.java:375)

at org.jivesoftware.openfire.IQRouter.route(IQRouter.java:122)

at org.jivesoftware.openfire.spi.PacketRouterImpl.route(PacketRouterImpl.java:76)

at org.jivesoftware.openfire.SessionPacketRouter.route(SessionPacketRouter.java:10 8)

at org.jivesoftware.openfire.SessionPacketRouter.route(SessionPacketRouter.java:69 )

at org.jivesoftware.openfire.http.HttpSession.sendPendingPackets(HttpSession.java: 626)

at org.jivesoftware.openfire.http.HttpSessionManager$HttpPacketSender.run(HttpSess ionManager.java:425)

at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)

at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)

at java.lang.Thread.run(Unknown Source)

I investigated the issue and found that when openfire try to clean the old session for that specific client, we go through removeSession (SessionManger.java) and there we try to get the presence of that remote old session

if (session == null) {
            session = getSession(fullJID);
        }
.
.
.
if (forceUnavailable || session.getPresence().isAvailable()) {
            Presence offline = new Presence();
            offline.setFrom(fullJID);
            offline.setTo(new JID(null, serverName, null, true));
            offline.setType(Presence.Type.unavailable);
            router.route(offline);
        }
.
.

the problem is that because the session is remote (old session was on node 1) and when we do getSession, we somehow get to this function getClientRoute (RoutingTableImpl.java):

public ClientSession getClientRoute(JID jid) {
        // Check if this session is hosted by this cluster node
        ClientSession session = (ClientSession) localRoutingTable.getRoute(jid.toString());
        if (session == null) {
            // The session is not in this JVM so assume remote
            RemoteSessionLocator locator = server.getRemoteSessionLocator();
            if (locator != null) {
                // Check if the session is hosted by other cluster node
                ClientRoute route = usersCache.get(jid.toString());
                if (route == null) {
                    route = anonymousUsersCache.get(jid.toString());
                }
                if (route != null) {
                    session = locator.getClientSession(route.getNodeID().toByteArray(), jid);
                }
            }
        }
        return session;
    }

and when we call getClientSession (line 14), we get to RemoteSessionLocator.java :

public ClientSession getClientSession(byte[] nodeID, JID address) {
        return new RemoteClientSession(nodeID, address);
    }

and here we return new session without presence (we never set the presence), and in the attached code above of *removeSession (line 7) * we get the NullPointerException.

I’m not sure if that is a bug or not, but I think when we catch the “error during login” in *IQBindHandler.java *we should close the old session anyway:

{
            String username = authToken.getUsername().toLowerCase();
            // If a session already exists with the requested JID, then check to see
            // if we should kick it off or refuse the new connection
            ClientSession oldSession = routingTable.getClientRoute(new JID(username, serverName, resource, true));
            if (oldSession != null) {
                try {
                    int conflictLimit = sessionManager.getConflictKickLimit();
                    if (conflictLimit == SessionManager.NEVER_KICK) {
                        reply.setChildElement(packet.getChildElement().createCopy());
                        reply.setError(PacketError.Condition.conflict);
                        // Send the error directly since a route does not exist at this point.
                        session.process(reply);
                        return null;
                    }                     int conflictCount = oldSession.incrementConflictCount();
                    if (conflictCount > conflictLimit) {
                        // Kick out the old connection that is conflicting with the new one
                        StreamError error = new StreamError(StreamError.Condition.conflict);
                        oldSession.deliverRawText(error.toXML());
                        oldSession.close();
                    }
                    else {
                        reply.setChildElement(packet.getChildElement().createCopy());
                        reply.setError(PacketError.Condition.conflict);
                        // Send the error directly since a route does not exist at this point.
                        session.process(reply);
                        return null;
                    }
                }
                catch (Exception e) {
                    Log.error("Error during login", e);
                    // Kick out the old connection that is conflicting with the new one
                    StreamError error = new StreamError(StreamError.Condition.conflict);
                    oldSession.deliverRawText(error.toXML());
                    oldSession.close();
                }
            }
            // If the connection was not refused due to conflict, log the user in
            session.setAuthToken(authToken, resource);
        }

what do you think guys ?