/**
   * Processes cache query response.
   *
   * @param sndId Sender node id.
   * @param res Query response.
   */
  @SuppressWarnings("unchecked")
  private void processQueryResponse(UUID sndId, GridCacheQueryResponse res) {
    if (log.isDebugEnabled()) log.debug("Received query response: " + res);

    GridCacheQueryFutureAdapter fut = getQueryFuture(res.requestId());

    if (fut != null)
      if (res.fields())
        ((GridCacheDistributedFieldsQueryFuture) fut)
            .onPage(
                sndId,
                res.metadata(),
                (Collection<Map<String, Object>>) ((Collection) res.data()),
                res.error(),
                res.isFinished());
      else fut.onPage(sndId, res.data(), res.error(), res.isFinished());
    else if (!cancelled.contains(res.requestId()))
      U.warn(
          log,
          "Received response for finished or unknown query [rmtNodeId="
              + sndId
              + ", res="
              + res
              + ']');
  }
  /** {@inheritDoc} */
  @Override
  protected boolean onPageReady(
      boolean loc, GridCacheQueryInfo qryInfo, Collection<?> data, boolean finished, Throwable e) {
    GridCacheLocalQueryFuture<?, ?, ?> fut = qryInfo.localQueryFuture();

    if (loc) assert fut != null;

    if (e != null) {
      if (loc) fut.onPage(null, null, e, true);
      else
        sendQueryResponse(
            qryInfo.senderId(),
            new GridCacheQueryResponse(cctx.cacheId(), qryInfo.requestId(), e),
            qryInfo.query().timeout());

      return true;
    }

    if (loc) fut.onPage(null, data, null, finished);
    else {
      GridCacheQueryResponse res =
          new GridCacheQueryResponse(
              cctx.cacheId(), qryInfo.requestId(), /*finished*/ false, /*fields*/ false);

      res.data(data);
      res.finished(finished);

      if (!sendQueryResponse(qryInfo.senderId(), res, qryInfo.query().timeout())) return false;
    }

    return true;
  }
  /** {@inheritDoc} */
  @SuppressWarnings("ConstantConditions")
  @Override
  protected boolean onFieldsPageReady(
      boolean loc,
      GridCacheQueryInfo qryInfo,
      @Nullable List<GridQueryFieldMetadata> metadata,
      @Nullable Collection<?> entities,
      @Nullable Collection<?> data,
      boolean finished,
      @Nullable Throwable e) {
    assert qryInfo != null;

    if (e != null) {
      if (loc) {
        GridCacheLocalFieldsQueryFuture fut =
            (GridCacheLocalFieldsQueryFuture) qryInfo.localQueryFuture();

        fut.onPage(null, null, null, e, true);
      } else
        sendQueryResponse(
            qryInfo.senderId(),
            new GridCacheQueryResponse(cctx.cacheId(), qryInfo.requestId(), e),
            qryInfo.query().timeout());

      return true;
    }

    if (loc) {
      GridCacheLocalFieldsQueryFuture fut =
          (GridCacheLocalFieldsQueryFuture) qryInfo.localQueryFuture();

      fut.onPage(null, metadata, data, null, finished);
    } else {
      GridCacheQueryResponse res =
          new GridCacheQueryResponse(
              cctx.cacheId(), qryInfo.requestId(), finished, qryInfo.reducer() == null);

      res.metadata(metadata);
      res.data(entities != null ? entities : data);

      if (!sendQueryResponse(qryInfo.senderId(), res, qryInfo.query().timeout())) return false;
    }

    return true;
  }
  /**
   * Sends cache query response.
   *
   * @param nodeId Node to send response.
   * @param res Cache query response.
   * @param timeout Message timeout.
   * @return {@code true} if response was sent, {@code false} otherwise.
   */
  private boolean sendQueryResponse(UUID nodeId, GridCacheQueryResponse res, long timeout) {
    ClusterNode node = cctx.node(nodeId);

    if (node == null) return false;

    int attempt = 1;

    IgniteCheckedException err = null;

    while (!Thread.currentThread().isInterrupted()) {
      try {
        if (log.isDebugEnabled()) log.debug("Send query response: " + res);

        Object topic = topic(nodeId, res.requestId());

        cctx.io()
            .sendOrderedMessage(
                node, topic, res, cctx.ioPolicy(), timeout > 0 ? timeout : Long.MAX_VALUE);

        return true;
      } catch (ClusterTopologyCheckedException ignored) {
        if (log.isDebugEnabled())
          log.debug(
              "Failed to send query response since node left grid [nodeId="
                  + nodeId
                  + ", res="
                  + res
                  + "]");

        return false;
      } catch (IgniteCheckedException e) {
        if (err == null) err = e;

        if (Thread.currentThread().isInterrupted()) break;

        if (attempt < RESEND_ATTEMPTS) {
          if (log.isDebugEnabled())
            log.debug(
                "Failed to send queries response (will try again) [nodeId="
                    + nodeId
                    + ", res="
                    + res
                    + ", attempt="
                    + attempt
                    + ", err="
                    + e
                    + "]");

          if (!Thread.currentThread().isInterrupted())
            try {
              U.sleep(RESEND_FREQ);
            } catch (IgniteInterruptedCheckedException e1) {
              U.error(
                  log,
                  "Waiting for queries response resending was interrupted (response will not be sent) "
                      + "[nodeId="
                      + nodeId
                      + ", response="
                      + res
                      + "]",
                  e1);

              return false;
            }
        } else {
          U.error(
              log,
              "Failed to sender cache response [nodeId=" + nodeId + ", response=" + res + "]",
              err);

          return false;
        }
      }

      attempt++;
    }

    return false;
  }