/**
     * @param newEntries Infos.
     * @param lsnr Listener for the operation future.
     * @throws GridInterruptedException If failed.
     * @return Future for operation.
     */
    @Nullable
    GridFutureAdapter<?> update(
        Iterable<Map.Entry<K, V>> newEntries, GridInClosure<GridFuture<?>> lsnr)
        throws GridInterruptedException {
      List<Map.Entry<K, V>> entries0 = null;
      GridFutureAdapter<Object> curFut0;

      synchronized (this) {
        curFut0 = curFut;

        curFut0.listenAsync(lsnr);

        for (Map.Entry<K, V> entry : newEntries) entries.add(entry);

        if (entries.size() >= bufSize) {
          entries0 = entries;

          entries = newEntries();
          curFut = new GridFutureAdapter<>(ctx);
          curFut.listenAsync(signalC);
        }
      }

      if (entries0 != null) {
        submit(entries0, curFut0);

        if (cancelled)
          curFut0.onDone(
              new GridException("Data loader has been cancelled: " + GridDataLoaderImpl.this));
      }

      return curFut0;
    }
    void cancelAll() {
      GridException err =
          new GridException("Data loader has been cancelled: " + GridDataLoaderImpl.this);

      for (GridFuture<?> f : locFuts) {
        try {
          f.cancel();
        } catch (GridException e) {
          U.error(log, "Failed to cancel mini-future.", e);
        }
      }

      for (GridFutureAdapter<?> f : reqs.values()) f.onDone(err);
    }
  /**
   * @param cancel {@code True} to close with cancellation.
   * @throws GridException If failed.
   */
  @Override
  public void close(boolean cancel) throws GridException {
    if (!closed.compareAndSet(false, true)) return;

    busyLock.block();

    if (log.isDebugEnabled())
      log.debug("Closing data loader [ldr=" + this + ", cancel=" + cancel + ']');

    GridException e = null;

    try {
      // Assuming that no methods are called on this loader after this method is called.
      if (cancel) {
        cancelled = true;

        for (Buffer buf : bufMappings.values()) buf.cancelAll();
      } else doFlush();

      ctx.event().removeLocalEventListener(discoLsnr);

      ctx.io().removeMessageListener(topic);
    } catch (GridException e0) {
      e = e0;
    }

    fut.onDone(null, e);

    if (e != null) throw e;
  }
    void onNodeLeft() {
      assert !isLocNode;
      assert bufMappings.get(node.id()) != this;

      if (log.isDebugEnabled())
        log.debug("Forcibly completing futures (node has left): " + node.id());

      Exception e =
          new GridTopologyException(
              "Failed to wait for request completion " + "(node has left): " + node.id());

      for (GridFutureAdapter<Object> f : reqs.values()) f.onDone(e);

      // Make sure to complete current future.
      GridFutureAdapter<Object> curFut0;

      synchronized (this) {
        curFut0 = curFut;
      }

      curFut0.onDone(e);
    }
    /** @param res Response. */
    void onResponse(GridDataLoadResponse res) {
      if (log.isDebugEnabled()) log.debug("Received data load response: " + res);

      GridFutureAdapter<?> f = reqs.remove(res.requestId());

      if (f == null) {
        if (log.isDebugEnabled())
          log.debug("Future for request has not been found: " + res.requestId());

        return;
      }

      Throwable err = null;

      byte[] errBytes = res.errorBytes();

      if (errBytes != null) {
        try {
          GridPeerDeployAware jobPda0 = jobPda;

          err =
              ctx.config()
                  .getMarshaller()
                  .unmarshal(
                      errBytes, jobPda0 != null ? jobPda0.classLoader() : U.gridClassLoader());
        } catch (GridException e) {
          f.onDone(null, new GridException("Failed to unmarshal response.", e));

          return;
        }
      }

      f.onDone(null, err);

      if (log.isDebugEnabled())
        log.debug(
            "Finished future [fut=" + f + ", reqId=" + res.requestId() + ", err=" + err + ']');
    }
  /** {@inheritDoc} */
  @Override
  public GridFuture<?> addData(Collection<? extends Map.Entry<K, V>> entries) {
    A.notEmpty(entries, "entries");

    enterBusy();

    try {
      GridFutureAdapter<Object> resFut = new GridFutureAdapter<>(ctx);

      activeFuts.add(resFut);

      resFut.listenAsync(rmvActiveFut);

      Collection<K> keys = new GridConcurrentHashSet<>(entries.size(), 1.0f, 16);

      for (Map.Entry<K, V> entry : entries) keys.add(entry.getKey());

      load0(entries, resFut, keys, 0);

      return resFut;
    } finally {
      leaveBusy();
    }
  }
    /** @param node Node. */
    Buffer(GridNode node) {
      assert node != null;

      this.node = node;

      locFuts = new GridConcurrentHashSet<>();
      reqs = new ConcurrentHashMap8<>();

      // Cache local node flag.
      isLocNode = node.equals(ctx.discovery().localNode());

      entries = newEntries();
      curFut = new GridFutureAdapter<>(ctx);
      curFut.listenAsync(signalC);

      sem = new Semaphore(parallelOps);
    }
    /**
     * @return Future if any submitted.
     * @throws GridInterruptedException If thread has been interrupted.
     */
    @Nullable
    GridFuture<?> flush() throws GridInterruptedException {
      List<Map.Entry<K, V>> entries0 = null;
      GridFutureAdapter<Object> curFut0 = null;

      synchronized (this) {
        if (!entries.isEmpty()) {
          entries0 = entries;
          curFut0 = curFut;

          entries = newEntries();
          curFut = new GridFutureAdapter<>(ctx);
          curFut.listenAsync(signalC);
        }
      }

      if (entries0 != null) submit(entries0, curFut0);

      // Create compound future for this flush.
      GridCompoundFuture<Object, Object> res = null;

      for (GridFuture<Object> f : locFuts) {
        if (res == null) res = new GridCompoundFuture<>(ctx);

        res.add(f);
      }

      for (GridFuture<Object> f : reqs.values()) {
        if (res == null) res = new GridCompoundFuture<>(ctx);

        res.add(f);
      }

      if (res != null) res.markInitialized();

      return res;
    }
 /** @return {@code true} If the loader is closed. */
 boolean isClosed() {
   return fut.isDone();
 }
  /**
   * @param entries Entries.
   * @param resFut Result future.
   * @param activeKeys Active keys.
   * @param remaps Remaps count.
   */
  private void load0(
      Collection<? extends Map.Entry<K, V>> entries,
      final GridFutureAdapter<Object> resFut,
      final Collection<K> activeKeys,
      final int remaps) {
    assert entries != null;

    if (remaps >= MAX_REMAP_CNT) {
      resFut.onDone(new GridException("Failed to finish operation (too many remaps): " + remaps));

      return;
    }

    Map<GridNode, Collection<Map.Entry<K, V>>> mappings = new HashMap<>();

    boolean initPda = ctx.deploy().enabled() && jobPda == null;

    for (Map.Entry<K, V> entry : entries) {
      GridNode node;

      try {
        K key = entry.getKey();

        assert key != null;

        if (initPda) {
          jobPda = new DataLoaderPda(key, entry.getValue(), updater);

          initPda = false;
        }

        node = ctx.affinity().mapKeyToNode(cacheName, key);
      } catch (GridException e) {
        resFut.onDone(e);

        return;
      }

      if (node == null) {
        resFut.onDone(
            new GridTopologyException(
                "Failed to map key to node "
                    + "(no nodes with cache found in topology) [infos="
                    + entries.size()
                    + ", cacheName="
                    + cacheName
                    + ']'));

        return;
      }

      Collection<Map.Entry<K, V>> col = mappings.get(node);

      if (col == null) mappings.put(node, col = new ArrayList<>());

      col.add(entry);
    }

    for (final Map.Entry<GridNode, Collection<Map.Entry<K, V>>> e : mappings.entrySet()) {
      final UUID nodeId = e.getKey().id();

      Buffer buf = bufMappings.get(nodeId);

      if (buf == null) {
        Buffer old = bufMappings.putIfAbsent(nodeId, buf = new Buffer(e.getKey()));

        if (old != null) buf = old;
      }

      final Collection<Map.Entry<K, V>> entriesForNode = e.getValue();

      GridInClosure<GridFuture<?>> lsnr =
          new GridInClosure<GridFuture<?>>() {
            @Override
            public void apply(GridFuture<?> t) {
              try {
                t.get();

                for (Map.Entry<K, V> e : entriesForNode) activeKeys.remove(e.getKey());

                if (activeKeys.isEmpty()) resFut.onDone();
              } catch (GridException e1) {
                if (log.isDebugEnabled())
                  log.debug("Future finished with error [nodeId=" + nodeId + ", err=" + e1 + ']');

                if (cancelled) {
                  resFut.onDone(
                      new GridException(
                          "Data loader has been cancelled: " + GridDataLoaderImpl.this, e1));
                } else load0(entriesForNode, resFut, activeKeys, remaps + 1);
              }
            }
          };

      GridFutureAdapter<?> f;

      try {
        f = buf.update(entriesForNode, lsnr);
      } catch (GridInterruptedException e1) {
        resFut.onDone(e1);

        return;
      }

      if (ctx.discovery().node(nodeId) == null) {
        if (bufMappings.remove(nodeId, buf)) buf.onNodeLeft();

        if (f != null)
          f.onDone(
              new GridTopologyException(
                  "Failed to wait for request completion " + "(node has left): " + nodeId));
      }
    }
  }