private void doExecute0(ClientRequestContext ctx, RpcRequest req,
                            RpcResponse returnedRes, CompletableFuture<RpcResponse> future) {
        final int totalAttempts = getTotalAttempts(ctx);
        final boolean initialAttempt = totalAttempts <= 1;
        if (returnedRes.isDone()) {
            // The response has been cancelled by the client before it receives a response, so stop retrying.
            handleException(ctx, future, new CancellationException(
                    "the response returned to the client has been cancelled"), initialAttempt);
            return;
        }
        if (!setResponseTimeout(ctx)) {
            handleException(ctx, future, ResponseTimeoutException.get(), initialAttempt);
            return;
        }

        final ClientRequestContext derivedCtx = newDerivedContext(ctx, null, req, initialAttempt);

        if (!initialAttempt) {
            derivedCtx.mutateAdditionalRequestHeaders(
                    mutator -> mutator.add(ARMERIA_RETRY_COUNT, StringUtil.toString(totalAttempts - 1)));
        }

        final RpcResponse res;

        final ClientRequestContextExtension ctxExtension = derivedCtx.as(ClientRequestContextExtension.class);
        final EndpointGroup endpointGroup = derivedCtx.endpointGroup();
        if (!initialAttempt && ctxExtension != null &&
            endpointGroup != null && derivedCtx.endpoint() == null) {
            // clear the pending throwable to retry endpoint selection
            ClientPendingThrowableUtil.removePendingThrowable(derivedCtx);
            // if the endpoint hasn't been selected, try to initialize the ctx with a new endpoint/event loop
            res = initContextAndExecuteWithFallback(unwrap(), ctxExtension, RpcResponse::from,
                                                    (context, cause) -> RpcResponse.ofFailure(cause),
                                                    req, true);
        } else {
            res = executeWithFallback(unwrap(), derivedCtx,
                                      (context, cause) -> RpcResponse.ofFailure(cause),
                                      req, true);
        }

        final RetryConfig<RpcResponse> retryConfig = mappedRetryConfig(ctx);
        final RetryRuleWithContent<RpcResponse> retryRule =
                retryConfig.needsContentInRule() ?
                retryConfig.retryRuleWithContent() : retryConfig.fromRetryRule();
        res.handle((unused1, cause) -> {
            try {
                assert retryRule != null;
                retryRule.shouldRetry(derivedCtx, res, cause).handle((decision, unused3) -> {
                    final Backoff backoff = decision != null ? decision.backoff() : null;
                    if (backoff != null) {
                        final long nextDelay = getNextDelay(derivedCtx, backoff);
                        if (nextDelay < 0) {
                            onRetryComplete(ctx, derivedCtx, res, future);
                            return null;
                        }

                        scheduleNextRetry(ctx, cause0 -> handleException(ctx, future, cause0, false),
                                          () -> doExecute0(ctx, req, returnedRes, future), nextDelay);
                    } else {
                        onRetryComplete(ctx, derivedCtx, res, future);
                    }
                    return null;
                });
            } catch (Throwable t) {
                handleException(ctx, future, t, false);
            }
            return null;
        });
    }
Creates a new instance that decorates the specified {@link RpcClient}.
core/src/main/java/com/linecorp/armeria/client/retry/RetryingRpcClient.java