docs: @Override
    public void run()
    {
      /**
       * Remember current time as the heartbeat start time. This is used for
       * calculating the delay for the next heartbeat.
       */
      this.lastHeartbeatStartTimeInSecs = System.currentTimeMillis() / 1000;
  
      Set<SFSession> sessionsToHeartbeat = new HashSet<SFSession>();
  
      // synchronously get a copy of the sessions from the global list
      synchronized (this)
      {
        sessionsToHeartbeat.addAll(sessions.keySet());
      }
  
      // heartbeat every session.
      for (SFSession session : sessionsToHeartbeat)
      {
        try
        {
          session.heartbeat();
        }
        catch (Throwable ex)
        {
          LOGGER.error("heartbeat error - message=" + ex.getMessage(), ex);
        }
      }
  
      /**
       * The following is synchronized with the methods to add or remove a
       * session so that we always make sure we have one heartbeat task scheduled
       * when there is any session left.
       */
      synchronized (this)
      {
        // schedule next heartbeat
        if (sessions.size() > 0)
        {
          LOGGER.debug("schedule next heartbeat run");
  
          scheduleHeartbeat();
        }
        else
        {
          LOGGER.debug("no need for heartbeat since no more sessions");
  
          // no need to heartbeat if no more session
          this.heartbeatFuture = null;
        }
      }
    }