0 Replies Latest reply on Jan 23, 2017 12:55 AM by Saleem

    Clustering plugin, error during login because of null presence

    Saleem

      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 ?