/**
     * @param fut Future.
     * @param doneCb Closure.
     */
    ChainFuture(
        GridFutureAdapter<R> fut, IgniteClosure<? super IgniteInternalFuture<R>, T> doneCb) {
      this.fut = fut;
      this.doneCb = doneCb;

      fut.listen(new GridFutureChainListener<>(this, doneCb));
    }
  /** @param nodeId Left node id. */
  public void onNodeLeft(final UUID nodeId) {
    if (isDone()) return;

    if (!enterBusy()) return;

    try {
      // Wait for initialization part of this future to complete.
      initFut.listen(
          new CI1<IgniteInternalFuture<?>>() {
            @Override
            public void apply(IgniteInternalFuture<?> f) {
              if (isDone()) return;

              if (!enterBusy()) return;

              try {
                // Pretend to have received message from this node.
                rcvdIds.add(nodeId);

                Collection<UUID> rmtIds = GridDhtPartitionsExchangeFuture.this.rmtIds;

                assert rmtIds != null;

                ClusterNode oldest = oldestNode.get();

                if (oldest.id().equals(nodeId)) {
                  if (log.isDebugEnabled())
                    log.debug(
                        "Oldest node left or failed on partition exchange "
                            + "(will restart exchange process)) [oldestNodeId="
                            + oldest.id()
                            + ", exchangeId="
                            + exchId
                            + ']');

                  boolean set = false;

                  ClusterNode newOldest =
                      CU.oldestAliveCacheServerNode(cctx, exchId.topologyVersion());

                  if (newOldest != null) {
                    // If local node is now oldest.
                    if (newOldest.id().equals(cctx.localNodeId())) {
                      synchronized (mux) {
                        if (oldestNode.compareAndSet(oldest, newOldest)) {
                          // If local node is just joining.
                          if (exchId.nodeId().equals(cctx.localNodeId())) {
                            try {
                              for (GridCacheContext cacheCtx : cctx.cacheContexts()) {
                                if (!cacheCtx.isLocal())
                                  cacheCtx
                                      .topology()
                                      .beforeExchange(GridDhtPartitionsExchangeFuture.this);
                              }
                            } catch (IgniteCheckedException e) {
                              onDone(e);

                              return;
                            }
                          }

                          set = true;
                        }
                      }
                    } else {
                      synchronized (mux) {
                        set = oldestNode.compareAndSet(oldest, newOldest);
                      }

                      if (set && log.isDebugEnabled())
                        log.debug(
                            "Reassigned oldest node [this="
                                + cctx.localNodeId()
                                + ", old="
                                + oldest.id()
                                + ", new="
                                + newOldest.id()
                                + ']');
                    }
                  }

                  if (set) {
                    // If received any messages, process them.
                    for (Map.Entry<UUID, GridDhtPartitionsSingleMessage> m : singleMsgs.entrySet())
                      onReceive(m.getKey(), m.getValue());

                    for (Map.Entry<UUID, GridDhtPartitionsFullMessage> m : fullMsgs.entrySet())
                      onReceive(m.getKey(), m.getValue());

                    // Reassign oldest node and resend.
                    recheck();
                  }
                } else if (rmtIds.contains(nodeId)) {
                  if (log.isDebugEnabled())
                    log.debug(
                        "Remote node left of failed during partition exchange (will ignore) "
                            + "[rmtNode="
                            + nodeId
                            + ", exchangeId="
                            + exchId
                            + ']');

                  assert rmtNodes != null;

                  for (Iterator<ClusterNode> it = rmtNodes.iterator(); it.hasNext(); ) {
                    if (it.next().id().equals(nodeId)) it.remove();
                  }

                  if (allReceived() && ready.get() && replied.compareAndSet(false, true))
                    if (spreadPartitions()) onDone(exchId.topologyVersion());
                }
              } finally {
                leaveBusy();
              }
            }
          });
    } finally {
      leaveBusy();
    }
  }
  /**
   * @param nodeId Sender node ID.
   * @param msg Full partition info.
   */
  public void onReceive(final UUID nodeId, final GridDhtPartitionsFullMessage msg) {
    assert msg != null;

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

      return;
    }

    if (log.isDebugEnabled())
      log.debug("Received full partition map from node [nodeId=" + nodeId + ", msg=" + msg + ']');

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

    initFut.listen(
        new CI1<IgniteInternalFuture<Boolean>>() {
          @Override
          public void apply(IgniteInternalFuture<Boolean> t) {
            ClusterNode curOldest = oldestNode.get();

            if (!nodeId.equals(curOldest.id())) {
              if (log.isDebugEnabled())
                log.debug(
                    "Received full partition map from unexpected node [oldest="
                        + curOldest.id()
                        + ", unexpectedNodeId="
                        + nodeId
                        + ']');

              ClusterNode snd = cctx.discovery().node(nodeId);

              if (snd == null) {
                if (log.isDebugEnabled())
                  log.debug(
                      "Sender node left grid, will ignore message from unexpected node [nodeId="
                          + nodeId
                          + ", exchId="
                          + msg.exchangeId()
                          + ']');

                return;
              }

              // Will process message later if sender node becomes oldest node.
              if (snd.order() > curOldest.order()) fullMsgs.put(nodeId, msg);

              return;
            }

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

            assert msg.lastVersion() != null;

            cctx.versions().onReceived(nodeId, msg.lastVersion());

            updatePartitionFullMap(msg);

            onDone(exchId.topologyVersion());
          }
        });
  }
  /**
   * @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);
              }
            }
          });
    }
  }