@Override
    public ClassicHttpResponse execute(
            final ClassicHttpRequest request,
            final Scope scope,
            final ExecChain chain) throws IOException, HttpException {
        Args.notNull(request, "request");
        Args.notNull(scope, "scope");
        final String exchangeId = scope.exchangeId;
        final HttpRoute route = scope.route;
        final HttpClientContext context = scope.clientContext;
        ClassicHttpRequest currentRequest = request;

        for (int execCount = 1;; execCount++) {
            final ClassicHttpResponse response;
            try {
                 response = chain.proceed(currentRequest, scope);
            } catch (final IOException ex) {
                if (scope.execRuntime.isExecutionAborted()) {
                    throw new RequestFailedException("Request aborted");
                }
                final HttpEntity requestEntity = request.getEntity();
                if (requestEntity != null && !requestEntity.isRepeatable()) {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("{} cannot retry non-repeatable request", exchangeId);
                    }
                    throw ex;
                }
                if (retryStrategy.retryRequest(request, ex, execCount, context)) {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("{} {}", exchangeId, ex.getMessage(), ex);
                    }
                    if (LOG.isInfoEnabled()) {
                        LOG.info("Recoverable I/O exception ({}) caught when processing request to {}",
                                ex.getClass().getName(), route);
                    }
                    final TimeValue nextInterval = retryStrategy.getRetryInterval(request, ex, execCount, context);
                    if (TimeValue.isPositive(nextInterval)) {
                        try {
                            if (LOG.isDebugEnabled()) {
                                LOG.debug("{} wait for {}", exchangeId, nextInterval);
                            }
                            nextInterval.sleep();
                        } catch (final InterruptedException e) {
                            Thread.currentThread().interrupt();
                            throw new InterruptedIOException();
                        }
                    }
                    currentRequest = ClassicRequestBuilder.copy(scope.originalRequest).build();
                    continue;
                }
                if (ex instanceof NoHttpResponseException) {
                    final NoHttpResponseException updatedex = new NoHttpResponseException(
                            route.getTargetHost().toHostString() + " failed to respond");
                    updatedex.setStackTrace(ex.getStackTrace());
                    throw updatedex;
                }
                throw ex;
            }

            try {
                final HttpEntity entity = request.getEntity();
                if (entity != null && !entity.isRepeatable()) {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("{} cannot retry non-repeatable request", exchangeId);
                    }
                    return response;
                }
                if (retryStrategy.retryRequest(response, execCount, context)) {
                    final TimeValue nextInterval = retryStrategy.getRetryInterval(response, execCount, context);
                    // Make sure the retry interval does not exceed the response timeout
                    if (TimeValue.isPositive(nextInterval)) {
                        final RequestConfig requestConfig = context.getRequestConfigOrDefault();
                        final Timeout responseTimeout = requestConfig.getResponseTimeout();
                        if (responseTimeout != null && nextInterval.compareTo(responseTimeout) > 0) {
                            return response;
                        }
                    }
                    response.close();
                    if (TimeValue.isPositive(nextInterval)) {
                        try {
                            if (LOG.isDebugEnabled()) {
                                LOG.debug("{} wait for {}", exchangeId, nextInterval);
                            }
                            nextInterval.sleep();
                        } catch (final InterruptedException e) {
                            Thread.currentThread().interrupt();
                            throw new InterruptedIOException();
                        }
                    }
                    currentRequest = ClassicRequestBuilder.copy(scope.originalRequest).build();
                } else {
                    return response;
                }
            } catch (final RuntimeException ex) {
                response.close();
                throw ex;
            }
        }
    }
Request executor in the request execution chain that is responsible for
making a decision whether a request that failed due to an I/O exception
or received a specific response from the target server should
be re-executed.
<p>
Further responsibilities such as communication with the opposite
endpoint is delegated to the next executor in the request execution
chain.
</p>
<p>
If this handler is active, pay particular attention to the placement
of other handlers within the handler chain relative to the retry handler.
Use {@link ChainElement#RETRY} as name when referring to this handler.
</p>
<p>
If a custom handler is placed <b>before</b> the retry handler, the handler will
see the initial request and the final outcome after the last retry. Elapsed time
will account for any delays imposed by the retry handler.
</p>

<p>
A custom handler which is placed <b>after</b> the retry handler will be invoked for
each individual retry. Elapsed time will measure each individual http request,
without the delay imposed by the retry handler.
</p>

@since 5.0
httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/HttpRequestRetryExec.java