@Override
  public void handleClusterView(boolean mergeView, int newViewId) {
    synchronized (viewHandlingLock) {
      // check to ensure this is not an older view
      if (newViewId <= viewId) {
        log.tracef("Ignoring old cluster view notification: %s", newViewId);
        return;
      }

      boolean becameCoordinator = !isCoordinator && transport.isCoordinator();
      isCoordinator = transport.isCoordinator();
      if (trace) {
        log.tracef(
            "Received new cluster view: %d, isCoordinator = %s, becameCoordinator = %s",
            (Object) newViewId, isCoordinator, becameCoordinator);
      }
      mustRecoverClusterStatus |= mergeView || becameCoordinator;
      if (!isCoordinator) return;

      if (mustRecoverClusterStatus) {
        // Clean up leftover cache status information from the last time we were coordinator.
        // E.g. if the local node was coordinator, started a rebalance, and then lost coordinator
        // status because of a merge, the existing cache statuses may have a rebalance in progress.
        cacheStatusMap.clear();
        try {
          recoverClusterStatus(newViewId, mergeView, transport.getMembers());
          mustRecoverClusterStatus = false;
        } catch (InterruptedException e) {
          log.tracef("Cluster state recovery interrupted because the coordinator is shutting down");
          // the CTMI has already stopped, no need to update the view id or notify waiters
          return;
        } catch (SuspectException e) {
          // We will retry when we receive the new view and then we'll reset the
          // mustRecoverClusterStatus flag
          return;
        } catch (Exception e) {
          if (!isShuttingDown) {
            log.failedToRecoverClusterState(e);
          } else {
            log.tracef("Cluster state recovery failed because the coordinator is shutting down");
          }
        }
      }

      // update the view id last, so join requests from other nodes wait until we recovered existing
      // members' info
      synchronized (viewUpdateLock) {
        viewId = newViewId;
        viewUpdateLock.notifyAll();
      }
    }

    if (!mustRecoverClusterStatus) {
      try {
        updateCacheMembers(transport.getMembers());
      } catch (Exception e) {
        log.errorUpdatingMembersList(e);
      }
    }
  }
  @Override
  public void handleClusterView(boolean mergeView, int newViewId) {
    synchronized (viewHandlingLock) {
      // check to ensure this is not an older view
      if (newViewId <= viewId) {
        log.tracef("Ignoring old cluster view notification: %s", newViewId);
        return;
      }

      boolean becameCoordinator = !isCoordinator && transport.isCoordinator();
      isCoordinator = transport.isCoordinator();
      log.tracef(
          "Received new cluster view: %s, isCoordinator = %s, becameCoordinator = %s",
          newViewId, isCoordinator, becameCoordinator);
      if (!isCoordinator) return;

      if (mergeView || becameCoordinator) {
        try {
          recoverClusterStatus(newViewId, mergeView, transport.getMembers());
        } catch (InterruptedException e) {
          log.tracef("Cluster state recovery interrupted because the coordinator is shutting down");
          // the CTMI has already stopped, no need to update the view id or notify waiters
          return;
        } catch (Exception e) {
          // TODO Retry?
          log.failedToRecoverClusterState(e);
        }
      } else {
        try {
          updateCacheMembers(transport.getMembers());
        } catch (Exception e) {
          log.errorUpdatingMembersList(e);
        }
      }

      // update the view id last, so join requests from other nodes wait until we recovered existing
      // members' info
      synchronized (viewUpdateLock) {
        viewId = newViewId;
        viewUpdateLock.notifyAll();
      }
    }
  }