protected void updateStateOnNodesLeaving(Collection<Address> leavers) {
    Set<GlobalTransaction> toKill = new HashSet<GlobalTransaction>();
    for (GlobalTransaction gt : remoteTransactions.keySet()) {
      if (leavers.contains(gt.getAddress())) toKill.add(gt);
    }

    if (toKill.isEmpty())
      log.tracef(
          "No global transactions pertain to originator(s) %s who have left the cluster.", leavers);
    else
      log.tracef(
          "%s global transactions pertain to leavers list %s and need to be killed",
          toKill.size(), leavers);

    for (GlobalTransaction gtx : toKill) {
      log.tracef("Killing remote transaction originating on leaver %s", gtx);
      RollbackCommand rc = new RollbackCommand(cacheName, gtx);
      rc.init(invoker, icc, TransactionTable.this);
      try {
        rc.perform(null);
        log.tracef("Rollback of transaction %s complete.", gtx);
      } catch (Throwable e) {
        log.unableToRollbackGlobalTx(gtx, e);
      }
    }

    log.trace("Completed cleaning transactions originating on leavers");
  }
    public CompletedTransactionStatus getTransactionStatus(GlobalTransaction gtx) {
      CompletedTransactionInfo completedTx = completedTransactions.get(gtx);
      if (completedTx != null) {
        return completedTx.successful
            ? CompletedTransactionStatus.COMMITTED
            : CompletedTransactionStatus.ABORTED;
      }

      // Transaction ids are allocated in sequence, so any transaction with a smaller id must have
      // been started
      // before a transaction that was already removed from the completed transactions map because
      // it was too old.
      // We assume that the transaction was either committed, or it was rolled back (e.g. because
      // the prepare
      // RPC timed out.
      // Note: We must check the id *after* verifying that the tx doesn't exist in the map.
      if (gtx.getId() > globalMaxPrunedTxId) return CompletedTransactionStatus.NOT_COMPLETED;
      Long nodeMaxPrunedTxId = nodeMaxPrunedTxIds.get(gtx.getAddress());
      if (nodeMaxPrunedTxId == null) {
        // We haven't removed any transaction for this node
        return CompletedTransactionStatus.NOT_COMPLETED;
      } else if (gtx.getId() > nodeMaxPrunedTxId) {
        // We haven't removed this particular transaction yet
        return CompletedTransactionStatus.NOT_COMPLETED;
      } else {
        // We already removed the status of this transaction from the completed transactions map
        return CompletedTransactionStatus.EXPIRED;
      }
    }
  public void cleanupLeaverTransactions(List<Address> members) {
    // Can happen if the cache is non-transactional
    if (remoteTransactions == null) return;

    if (trace)
      log.tracef(
          "Checking for transactions originated on leavers. Current cache members are %s, remote transactions: %d",
          members, remoteTransactions.size());
    HashSet<Address> membersSet = new HashSet<>(members);
    List<GlobalTransaction> toKill = new ArrayList<>();
    for (Map.Entry<GlobalTransaction, RemoteTransaction> e : remoteTransactions.entrySet()) {
      GlobalTransaction gt = e.getKey();
      if (trace) log.tracef("Checking transaction %s", gt);
      if (!membersSet.contains(gt.getAddress())) {
        toKill.add(gt);
      }
    }

    if (toKill.isEmpty()) {
      if (trace)
        log.tracef("No remote transactions pertain to originator(s) who have left the cluster.");
    } else {
      log.debugf("The originating node left the cluster for %d remote transactions", toKill.size());
      for (GlobalTransaction gtx : toKill) {
        if (partitionHandlingManager.canRollbackTransactionAfterOriginatorLeave(gtx)) {
          log.debugf(
              "Rolling back transaction %s because originator %s left the cluster",
              gtx, gtx.getAddress());
          killTransaction(gtx);
        } else {
          log.debugf(
              "Keeping transaction %s after the originator %s left the cluster.",
              gtx, gtx.getAddress());
        }
      }

      if (trace)
        log.tracef(
            "Completed cleaning transactions originating on leavers. Remote transactions remaining: %d",
            remoteTransactions.size());
    }
  }
    /** @see #markTransactionCompleted(GlobalTransaction, boolean) */
    public boolean isTransactionCompleted(GlobalTransaction gtx) {
      if (completedTransactions.containsKey(gtx)) return true;

      // Transaction ids are allocated in sequence, so any transaction with a smaller id must have
      // been started
      // before a transaction that was already removed from the completed transactions map because
      // it was too old.
      // We assume that the transaction was either committed, or it was rolled back (e.g. because
      // the prepare
      // RPC timed out.
      // Note: We must check the id *after* verifying that the tx doesn't exist in the map.
      if (gtx.getId() > globalMaxPrunedTxId) return false;
      Long nodeMaxPrunedTxId = nodeMaxPrunedTxIds.get(gtx.getAddress());
      return nodeMaxPrunedTxId != null && gtx.getId() <= nodeMaxPrunedTxId;
    }
  public void cleanupStaleTransactions(CacheTopology cacheTopology) {
    int topologyId = cacheTopology.getTopologyId();
    List<Address> members = cacheTopology.getMembers();

    // We only care about transactions originated before this topology update
    if (getMinTopologyId() >= topologyId) return;

    log.tracef(
        "Checking for transactions originated on leavers. Current members are %s, remote transactions: %d",
        members, remoteTransactions.size());
    Set<GlobalTransaction> toKill = new HashSet<GlobalTransaction>();
    for (Map.Entry<GlobalTransaction, RemoteTransaction> e : remoteTransactions.entrySet()) {
      GlobalTransaction gt = e.getKey();
      RemoteTransaction remoteTx = e.getValue();
      log.tracef("Checking transaction %s", gt);
      // The topology id check is needed for joiners
      if (remoteTx.getTopologyId() < topologyId && !members.contains(gt.getAddress())) {
        toKill.add(gt);
      }
    }

    if (toKill.isEmpty()) {
      log.tracef("No global transactions pertain to originator(s) who have left the cluster.");
    } else {
      log.tracef("%s global transactions pertain to leavers and need to be killed", toKill.size());
    }

    for (GlobalTransaction gtx : toKill) {
      log.tracef("Killing remote transaction originating on leaver %s", gtx);
      RollbackCommand rc = new RollbackCommand(cacheName, gtx);
      rc.init(invoker, icc, TransactionTable.this);
      try {
        rc.perform(null);
        log.tracef("Rollback of transaction %s complete.", gtx);
      } catch (Throwable e) {
        log.unableToRollbackGlobalTx(gtx, e);
      }
    }

    log.tracef(
        "Completed cleaning transactions originating on leavers. Remote transactions remaining: %d",
        remoteTransactions.size());
  }