protected AsyncAction(Request request, ActionListener<Response> listener) {
      this.request = request;
      this.listener = listener;

      clusterState = clusterService.state();
      nodes = clusterState.nodes();

      ClusterBlockException globalBlockException = checkGlobalBlock(clusterState, request);
      if (globalBlockException != null) {
        throw globalBlockException;
      }

      String[] concreteIndices = indexNameExpressionResolver.concreteIndices(clusterState, request);
      ClusterBlockException requestBlockException =
          checkRequestBlock(clusterState, request, concreteIndices);
      if (requestBlockException != null) {
        throw requestBlockException;
      }

      logger.trace(
          "resolving shards for [{}] based on cluster state version [{}]",
          actionName,
          clusterState.version());
      ShardsIterator shardIt = shards(clusterState, request, concreteIndices);
      nodeIds = new HashMap<>();

      for (ShardRouting shard : shardIt.asUnordered()) {
        if (shard.assignedToNode()) {
          String nodeId = shard.currentNodeId();
          if (!nodeIds.containsKey(nodeId)) {
            nodeIds.put(nodeId, new ArrayList<>());
          }
          nodeIds.get(nodeId).add(shard);
        } else {
          unavailableShardExceptions.add(
              new NoShardAvailableActionException(
                  shard.shardId(),
                  " no shards available for shard "
                      + shard.toString()
                      + " while executing "
                      + actionName));
        }
      }

      responses = new AtomicReferenceArray<>(nodeIds.size());
    }
    private void perform(final Throwable lastException) {
      final ShardRouting shard = shardsIt == null ? null : shardsIt.nextOrNull();
      if (shard == null) {
        Throwable failure = lastException;
        if (failure == null) {
          failure =
              new NoShardAvailableActionException(
                  null, "No shard available for [" + internalRequest.request() + "]");
        } else {
          if (logger.isDebugEnabled()) {
            logger.debug("failed to execute [" + internalRequest.request() + "]", failure);
          }
        }
        listener.onFailure(failure);
      } else {
        if (shard.currentNodeId().equals(nodes.localNodeId())) {
          // we don't prefer local shard, so try and do it here
          if (!internalRequest.request().preferLocalShard()) {
            try {
              if (internalRequest.request().operationThreaded()) {
                internalRequest.request().beforeLocalFork();
                threadPool
                    .executor(executor)
                    .execute(
                        new Runnable() {
                          @Override
                          public void run() {
                            try {
                              Response response =
                                  shardOperation(internalRequest.request(), shard.shardId());
                              listener.onResponse(response);
                            } catch (Throwable e) {
                              onFailure(shard, e);
                            }
                          }
                        });
              } else {
                final Response response =
                    shardOperation(internalRequest.request(), shard.shardId());
                listener.onResponse(response);
              }
            } catch (Throwable e) {
              onFailure(shard, e);
            }
          } else {
            perform(lastException);
          }
        } else {
          DiscoveryNode node = nodes.get(shard.currentNodeId());
          transportService.sendRequest(
              node,
              transportShardAction,
              new ShardSingleOperationRequest(internalRequest.request(), shard.shardId()),
              new BaseTransportResponseHandler<Response>() {
                @Override
                public Response newInstance() {
                  return newResponse();
                }

                @Override
                public String executor() {
                  return ThreadPool.Names.SAME;
                }

                @Override
                public void handleResponse(final Response response) {
                  listener.onResponse(response);
                }

                @Override
                public void handleException(TransportException exp) {
                  onFailure(shard, exp);
                }
              });
        }
      }
    }
    private void perform(@Nullable final Throwable currentFailure) {
      Throwable lastFailure = this.lastFailure;
      if (lastFailure == null || TransportActions.isReadOverrideException(currentFailure)) {
        lastFailure = currentFailure;
        this.lastFailure = currentFailure;
      }
      final ShardRouting shardRouting = shardIt.nextOrNull();
      if (shardRouting == null) {
        Throwable failure = lastFailure;
        if (failure == null || isShardNotAvailableException(failure)) {
          failure =
              new NoShardAvailableActionException(
                  null,
                  LoggerMessageFormat.format(
                      "No shard available for [{}]", internalRequest.request()),
                  failure);
        } else {
          if (logger.isDebugEnabled()) {
            logger.debug("{}: failed to execute [{}]", failure, null, internalRequest.request());
          }
        }
        listener.onFailure(failure);
        return;
      }
      DiscoveryNode node = nodes.get(shardRouting.currentNodeId());
      if (node == null) {
        onFailure(shardRouting, new NoShardAvailableActionException(shardRouting.shardId()));
      } else {
        internalRequest.request().internalShardId = shardRouting.shardId();
        if (logger.isTraceEnabled()) {
          logger.trace(
              "sending request [{}] to shard [{}] on node [{}]",
              internalRequest.request(),
              internalRequest.request().internalShardId,
              node);
        }
        transportService.sendRequest(
            node,
            transportShardAction,
            internalRequest.request(),
            new BaseTransportResponseHandler<Response>() {

              @Override
              public Response newInstance() {
                return newResponse();
              }

              @Override
              public String executor() {
                return ThreadPool.Names.SAME;
              }

              @Override
              public void handleResponse(final Response response) {
                listener.onResponse(response);
              }

              @Override
              public void handleException(TransportException exp) {
                onFailure(shardRouting, exp);
              }
            });
      }
    }
    /** First get should try and use a shard that exists on a local node for better performance */
    private void performFirst() {
      if (shardsIt == null) {
        // just execute it on the local node
        if (internalRequest.request().operationThreaded()) {
          internalRequest.request().beforeLocalFork();
          threadPool
              .executor(executor())
              .execute(
                  new Runnable() {
                    @Override
                    public void run() {
                      try {
                        Response response = shardOperation(internalRequest.request(), null);
                        listener.onResponse(response);
                      } catch (Throwable e) {
                        onFailure(null, e);
                      }
                    }
                  });
          return;
        } else {
          try {
            final Response response = shardOperation(internalRequest.request(), null);
            listener.onResponse(response);
            return;
          } catch (Throwable e) {
            onFailure(null, e);
          }
        }
        return;
      }

      if (internalRequest.request().preferLocalShard()) {
        boolean foundLocal = false;
        ShardRouting shardX;
        while ((shardX = shardsIt.nextOrNull()) != null) {
          final ShardRouting shard = shardX;
          if (shard.currentNodeId().equals(nodes.localNodeId())) {
            foundLocal = true;
            if (internalRequest.request().operationThreaded()) {
              internalRequest.request().beforeLocalFork();
              threadPool
                  .executor(executor())
                  .execute(
                      new Runnable() {
                        @Override
                        public void run() {
                          try {
                            Response response =
                                shardOperation(internalRequest.request(), shard.shardId());
                            listener.onResponse(response);
                          } catch (Throwable e) {
                            shardsIt.reset();
                            onFailure(shard, e);
                          }
                        }
                      });
              return;
            } else {
              try {
                final Response response =
                    shardOperation(internalRequest.request(), shard.shardId());
                listener.onResponse(response);
                return;
              } catch (Throwable e) {
                shardsIt.reset();
                onFailure(shard, e);
              }
            }
          }
        }
        if (!foundLocal) {
          // no local node get, go remote
          shardsIt.reset();
          perform(null);
        }
      } else {
        perform(null);
      }
    }