/**
   * Cache preloader should call this method within partition lock.
   *
   * @param key Key.
   * @param ver Version.
   * @return {@code True} if preloading is permitted.
   */
  public boolean preloadingPermitted(KeyCacheObject key, GridCacheVersion ver) {
    assert key != null;
    assert ver != null;
    assert lock.isHeldByCurrentThread(); // Only one thread can enter this method at a time.

    if (state() != MOVING) return false;

    Map<KeyCacheObject, GridCacheVersion> evictHist0 = evictHist;

    if (evictHist0 != null) {
      GridCacheVersion ver0 = evictHist0.get(key);

      // Permit preloading if version in history
      // is missing or less than passed in.
      return ver0 == null || ver0.isLess(ver);
    }

    return false;
  }
  /**
   * @param key Key.
   * @param ver Version.
   */
  public void onEntryEvicted(KeyCacheObject key, GridCacheVersion ver) {
    assert key != null;
    assert ver != null;
    assert lock.isHeldByCurrentThread(); // Only one thread can enter this method at a time.

    if (state() != MOVING) return;

    Map<KeyCacheObject, GridCacheVersion> evictHist0 = evictHist;

    if (evictHist0 != null) {
      GridCacheVersion ver0 = evictHist0.get(key);

      if (ver0 == null || ver0.isLess(ver)) {
        GridCacheVersion ver1 = evictHist0.put(key, ver);

        assert ver1 == ver0;
      }
    }
  }
  /**
   * @param nodeId Sender node id.
   * @param msg Single partition info.
   */
  public void onReceive(final UUID nodeId, final GridDhtPartitionsSingleMessage msg) {
    assert msg != null;

    assert msg.exchangeId().equals(exchId);

    // Update last seen version.
    while (true) {
      GridCacheVersion old = lastVer.get();

      if (old == null || old.compareTo(msg.lastVersion()) < 0) {
        if (lastVer.compareAndSet(old, msg.lastVersion())) break;
      } else break;
    }

    if (isDone()) {
      if (log.isDebugEnabled())
        log.debug(
            "Received message for finished future (will reply only to sender) [msg="
                + msg
                + ", fut="
                + this
                + ']');

      sendAllPartitions(nodeId, cctx.gridConfig().getNetworkSendRetryCount());
    } else {
      initFut.listen(
          new CI1<IgniteInternalFuture<Boolean>>() {
            @Override
            public void apply(IgniteInternalFuture<Boolean> t) {
              try {
                if (!t.get()) // Just to check if there was an error.
                return;

                ClusterNode loc = cctx.localNode();

                singleMsgs.put(nodeId, msg);

                boolean match = true;

                // Check if oldest node has changed.
                if (!oldestNode.get().equals(loc)) {
                  match = false;

                  synchronized (mux) {
                    // Double check.
                    if (oldestNode.get().equals(loc)) match = true;
                  }
                }

                if (match) {
                  boolean allReceived;

                  synchronized (rcvdIds) {
                    if (rcvdIds.add(nodeId)) updatePartitionSingleMap(msg);

                    allReceived = allReceived();
                  }

                  // If got all replies, and initialization finished, and reply has not been sent
                  // yet.
                  if (allReceived && ready.get() && replied.compareAndSet(false, true)) {
                    spreadPartitions();

                    onDone(exchId.topologyVersion());
                  } else if (log.isDebugEnabled())
                    log.debug(
                        "Exchange future full map is not sent [allReceived="
                            + allReceived()
                            + ", ready="
                            + ready
                            + ", replied="
                            + replied.get()
                            + ", init="
                            + init.get()
                            + ", fut="
                            + this
                            + ']');
                }
              } catch (IgniteCheckedException e) {
                U.error(log, "Failed to initialize exchange future: " + this, e);
              }
            }
          });
    }
  }