/** @return {@code true} if no keys are tracked, {@code false} otherwise. */
 public final boolean isEmpty() {
   return tracker.isEmpty();
 }
    public void cleanupCompletedTransactions() {
      if (completedTransactions.isEmpty()) return;

      try {
        if (trace)
          log.tracef(
              "About to cleanup completed transaction. Initial size is %d",
              completedTransactions.size());
        long beginning = timeService.time();
        long minCompleteTimestamp =
            timeService.time()
                - TimeUnit.MILLISECONDS.toNanos(configuration.transaction().completedTxTimeout());
        int removedEntries = 0;

        // Collect the leavers. They will be removed at the end.
        Set<Address> leavers = new HashSet<>();
        for (Map.Entry<Address, Long> e : nodeMaxPrunedTxIds.entrySet()) {
          if (!rpcManager.getMembers().contains(e.getKey())) {
            leavers.add(e.getKey());
          }
        }

        // Remove stale completed transactions.
        Iterator<Map.Entry<GlobalTransaction, CompletedTransactionInfo>> txIterator =
            completedTransactions.entrySet().iterator();
        while (txIterator.hasNext()) {
          Map.Entry<GlobalTransaction, CompletedTransactionInfo> e = txIterator.next();
          CompletedTransactionInfo completedTx = e.getValue();
          if (minCompleteTimestamp - completedTx.timestamp > 0) {
            // Need to update lastPrunedTxId *before* removing the tx from the map
            // Don't need atomic operations, there can't be more than one thread updating
            // lastPrunedTxId.
            final long txId = e.getKey().getId();
            final Address address = e.getKey().getAddress();
            updateLastPrunedTxId(txId, address);

            txIterator.remove();
            removedEntries++;
          } else {
            // Nodes with "active" completed transactions are not removed..
            leavers.remove(e.getKey().getAddress());
          }
        }

        // Finally, remove nodes that are no longer members and don't have any "active" completed
        // transactions.
        for (Address e : leavers) {
          nodeMaxPrunedTxIds.remove(e);
        }

        long duration = timeService.timeDuration(beginning, TimeUnit.MILLISECONDS);

        if (trace)
          log.tracef(
              "Finished cleaning up completed transactions in %d millis, %d transactions were removed, "
                  + "current number of completed transactions is %d",
              removedEntries, duration, completedTransactions.size());
        if (trace)
          log.tracef(
              "Last pruned transaction ids were updated: %d, %s",
              globalMaxPrunedTxId, nodeMaxPrunedTxIds);
      } catch (Exception e) {
        log.errorf(e, "Failed to cleanup completed transactions: %s", e.getMessage());
      }
    }