public <T extends TransportResponse> void sendRequest(
      final DiscoveryNode node,
      final String action,
      final TransportRequest request,
      final TransportRequestOptions options,
      TransportResponseHandler<T> handler) {
    if (node == null) {
      throw new ElasticsearchIllegalStateException("can't send request to a null node");
    }
    final long requestId = newRequestId();
    TimeoutHandler timeoutHandler = null;
    try {
      clientHandlers.put(requestId, new RequestHolder<>(handler, node, action, timeoutHandler));
      if (started.get() == false) {
        // if we are not started the exception handling will remove the RequestHolder again and
        // calls the handler to notify the caller.
        // it will only notify if the toStop code hasn't done the work yet.
        throw new TransportException("TransportService is closed stopped can't send request");
      }
      if (options.timeout() != null) {
        timeoutHandler = new TimeoutHandler(requestId);
        timeoutHandler.future =
            threadPool.schedule(options.timeout(), ThreadPool.Names.GENERIC, timeoutHandler);
      }
      transport.sendRequest(node, requestId, action, request, options);
    } catch (final Throwable e) {
      // usually happen either because we failed to connect to the node
      // or because we failed serializing the message
      final RequestHolder holderToNotify = clientHandlers.remove(requestId);
      // if the scheduler raise a EsRejectedExecutionException (due to shutdown), we may have a
      // timeout handler, but no future
      if (timeoutHandler != null) {
        FutureUtils.cancel(timeoutHandler.future);
      }

      // If holderToNotify == null then handler has already been taken care of.
      if (holderToNotify != null) {
        // callback that an exception happened, but on a different thread since we don't
        // want handlers to worry about stack overflows
        final SendRequestTransportException sendRequestException =
            new SendRequestTransportException(node, action, e);
        threadPool
            .executor(ThreadPool.Names.GENERIC)
            .execute(
                new Runnable() {
                  @Override
                  public void run() {
                    holderToNotify.handler().handleException(sendRequestException);
                  }
                });
      }
    }
  }
    @Override
    protected void doSample() {
      HashSet<DiscoveryNode> newNodes = new HashSet<>();
      HashSet<DiscoveryNode> newFilteredNodes = new HashSet<>();
      for (DiscoveryNode listedNode : listedNodes) {
        if (!transportService.nodeConnected(listedNode)) {
          try {
            // its a listed node, light connect to it...
            logger.trace("connecting to listed node (light) [{}]", listedNode);
            transportService.connectToNodeLight(listedNode);
          } catch (Throwable e) {
            logger.debug("failed to connect to node [{}], removed from nodes list", e, listedNode);
            continue;
          }
        }
        try {
          LivenessResponse livenessResponse =
              transportService
                  .submitRequest(
                      listedNode,
                      TransportLivenessAction.NAME,
                      headers.applyTo(new LivenessRequest()),
                      TransportRequestOptions.options()
                          .withType(TransportRequestOptions.Type.STATE)
                          .withTimeout(pingTimeout),
                      new FutureTransportResponseHandler<LivenessResponse>() {
                        @Override
                        public LivenessResponse newInstance() {
                          return new LivenessResponse();
                        }
                      })
                  .txGet();
          if (!ignoreClusterName && !clusterName.equals(livenessResponse.getClusterName())) {
            logger.warn("node {} not part of the cluster {}, ignoring...", listedNode, clusterName);
            newFilteredNodes.add(listedNode);
          } else if (livenessResponse.getDiscoveryNode() != null) {
            // use discovered information but do keep the original transport address, so people can
            // control which address is exactly used.
            DiscoveryNode nodeWithInfo = livenessResponse.getDiscoveryNode();
            newNodes.add(
                new DiscoveryNode(
                    nodeWithInfo.name(),
                    nodeWithInfo.id(),
                    nodeWithInfo.getHostName(),
                    nodeWithInfo.getHostAddress(),
                    listedNode.address(),
                    nodeWithInfo.attributes(),
                    nodeWithInfo.version()));
          } else {
            // although we asked for one node, our target may not have completed initialization yet
            // and doesn't have cluster nodes
            logger.debug(
                "node {} didn't return any discovery info, temporarily using transport discovery node",
                listedNode);
            newNodes.add(listedNode);
          }
        } catch (Throwable e) {
          logger.info("failed to get node info for {}, disconnecting...", e, listedNode);
          transportService.disconnectFromNode(listedNode);
        }
      }

      nodes = validateNewNodes(newNodes);
      filteredNodes = ImmutableList.copyOf(newFilteredNodes);
    }