docs: protected <T, E extends Throwable> T doExecute(RetryCallback<T, E> retryCallback,
  			RecoveryCallback<T> recoveryCallback, RetryState state)
  			throws E, ExhaustedRetryException {
  
  		RetryPolicy retryPolicy = this.retryPolicy;
  		BackOffPolicy backOffPolicy = this.backOffPolicy;
  
  		// Allow the retry policy to initialise itself...
  		RetryContext context = open(retryPolicy, state);
  		if (this.logger.isTraceEnabled()) {
  			this.logger.trace("RetryContext retrieved: " + context);
  		}
  
  		// Make sure the context is available globally for clients who need
  		// it...
  		RetrySynchronizationManager.register(context);
  
  		Throwable lastException = null;
  
  		boolean exhausted = false;
  		try {
  
  			// Give clients a chance to enhance the context...
  			boolean running = doOpenInterceptors(retryCallback, context);
  
  			if (!running) {
  				throw new TerminatedRetryException(
  						"Retry terminated abnormally by interceptor before first attempt");
  			}
  
  			// Get or Start the backoff context...
  			BackOffContext backOffContext = null;
  			Object resource = context.getAttribute("backOffContext");
  
  			if (resource instanceof BackOffContext) {
  				backOffContext = (BackOffContext) resource;
  			}
  
  			if (backOffContext == null) {
  				backOffContext = backOffPolicy.start(context);
  				if (backOffContext != null) {
  					context.setAttribute("backOffContext", backOffContext);
  				}
  			}
  
  			/*
  			 * We allow the whole loop to be skipped if the policy or context already
  			 * forbid the first try. This is used in the case of external retry to allow a
  			 * recovery in handleRetryExhausted without the callback processing (which
  			 * would throw an exception).
  			 */
  			while (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {
  
  				try {
  					if (this.logger.isDebugEnabled()) {
  						this.logger.debug("Retry: count=" + context.getRetryCount());
  					}
  					// Reset the last exception, so if we are successful
  					// the close interceptors will not think we failed...
  					lastException = null;
  					return retryCallback.doWithRetry(context);
  				}
  				catch (Throwable e) {
  
  					lastException = e;
  
  					try {
  						registerThrowable(retryPolicy, state, context, e);
  					}
  					catch (Exception ex) {
  						throw new TerminatedRetryException("Could not register throwable",
  								ex);
  					}
  					finally {
  						doOnErrorInterceptors(retryCallback, context, e);
  					}
  
  					if (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {
  						try {
  							backOffPolicy.backOff(backOffContext);
  						}
  						catch (BackOffInterruptedException ex) {
  							lastException = e;
  							// back off was prevented by another thread - fail the retry
  							if (this.logger.isDebugEnabled()) {
  								this.logger
  										.debug("Abort retry because interrupted: count="
  												+ context.getRetryCount());
  							}
  							throw ex;
  						}
  					}
  
  					if (this.logger.isDebugEnabled()) {
  						this.logger.debug(
  								"Checking for rethrow: count=" + context.getRetryCount());
  					}
  
  					if (shouldRethrow(retryPolicy, context, state)) {
  						if (this.logger.isDebugEnabled()) {
  							this.logger.debug("Rethrow in retry for policy: count="
  									+ context.getRetryCount());
  						}
  						throw RetryTemplate.<E>wrapIfNecessary(e);
  					}
  
  				}
  
  				/*
  				 * A stateful attempt that can retry may rethrow the exception before now,
  				 * but if we get this far in a stateful retry there's a reason for it,
  				 * like a circuit breaker or a rollback classifier.
  				 */
  				if (state != null && context.hasAttribute(GLOBAL_STATE)) {
  					break;
  				}
  			}
  
  			if (state == null && this.logger.isDebugEnabled()) {
  				this.logger.debug(
  						"Retry failed last attempt: count=" + context.getRetryCount());
  			}
  
  			exhausted = true;
  			return handleRetryExhausted(recoveryCallback, context, state);
  
  		}
  		catch (Throwable e) {
  			throw RetryTemplate.<E>wrapIfNecessary(e);
  		}
  		finally {
  			close(retryPolicy, context, state, lastException == null || exhausted);
  			doCloseInterceptors(retryCallback, context, lastException);
  			RetrySynchronizationManager.clear();
  		}
  
  	}