private AsyncAction(
        SearchScrollRequest request,
        ParsedScrollId scrollId,
        ActionListener<SearchResponse> listener) {
      this.request = request;
      this.listener = listener;
      this.scrollId = scrollId;
      this.nodes = clusterService.state().nodes();
      this.successfulOps = new AtomicInteger(scrollId.getContext().length);

      this.queryResults = new AtomicArray<QuerySearchResult>(scrollId.getContext().length);
      this.fetchResults = new AtomicArray<FetchSearchResult>(scrollId.getContext().length);
    }
    public void start() {
      if (scrollId.getContext().length == 0) {
        listener.onFailure(
            new SearchPhaseExecutionException("query", "no nodes to search on", null));
        return;
      }
      final AtomicInteger counter = new AtomicInteger(scrollId.getContext().length);

      int localOperations = 0;
      for (Tuple<String, Long> target : scrollId.getContext()) {
        DiscoveryNode node = nodes.get(target.v1());
        if (node != null) {
          if (nodes.localNodeId().equals(node.id())) {
            localOperations++;
          } else {
            executeQueryPhase(counter, node, target.v2());
          }
        } else {
          if (logger.isDebugEnabled()) {
            logger.debug(
                "Node ["
                    + target.v1()
                    + "] not available for scroll request ["
                    + scrollId.getSource()
                    + "]");
          }
          successfulOps.decrementAndGet();
          if (counter.decrementAndGet() == 0) {
            executeFetchPhase();
          }
        }
      }

      if (localOperations > 0) {
        if (request.getOperationThreading() == SearchOperationThreading.SINGLE_THREAD) {
          threadPool
              .executor(ThreadPool.Names.SEARCH)
              .execute(
                  new Runnable() {
                    @Override
                    public void run() {
                      for (Tuple<String, Long> target : scrollId.getContext()) {
                        DiscoveryNode node = nodes.get(target.v1());
                        if (node != null && nodes.localNodeId().equals(node.id())) {
                          executeQueryPhase(counter, node, target.v2());
                        }
                      }
                    }
                  });
        } else {
          boolean localAsync =
              request.getOperationThreading() == SearchOperationThreading.THREAD_PER_SHARD;
          for (final Tuple<String, Long> target : scrollId.getContext()) {
            final DiscoveryNode node = nodes.get(target.v1());
            if (node != null && nodes.localNodeId().equals(node.id())) {
              if (localAsync) {
                threadPool
                    .executor(ThreadPool.Names.SEARCH)
                    .execute(
                        new Runnable() {
                          @Override
                          public void run() {
                            executeQueryPhase(counter, node, target.v2());
                          }
                        });
              } else {
                executeQueryPhase(counter, node, target.v2());
              }
            }
          }
        }
      }
    }
 // we do our best to return the shard failures, but its ok if its not fully concurrently safe
 // we simply try and return as much as possible
 protected final void addShardFailure(final int shardIndex, ShardSearchFailure failure) {
   if (shardFailures == null) {
     shardFailures = new AtomicArray<ShardSearchFailure>(scrollId.getContext().length);
   }
   shardFailures.set(shardIndex, failure);
 }