/* * Copyright 2010-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ using Amazon.Runtime.Internal.Util; using System; using System.Net; namespace Amazon.Runtime.Internal { /// /// The retry handler has the generic logic for retrying requests. /// It uses a retry policy which specifies when /// a retry should be performed. /// public class RetryHandler : PipelineHandler { private ILogger _logger; /// /// The logger used to log messages. /// public override ILogger Logger { get { return _logger; } set { _logger = value; this.RetryPolicy.Logger = value; } } /// /// The retry policy which specifies when /// a retry should be performed. /// public RetryPolicy RetryPolicy { get; private set; } /// /// Constructor which takes in a retry policy. /// /// Retry Policy public RetryHandler(RetryPolicy retryPolicy) { this.RetryPolicy = retryPolicy; } /// /// Invokes the inner handler and performs a retry, if required as per the /// retry policy. /// /// The execution context which contains both the /// requests and response context. public override void InvokeSync(IExecutionContext executionContext) { var requestContext = executionContext.RequestContext; var responseContext = executionContext.ResponseContext; bool shouldRetry = false; do { try { base.InvokeSync(executionContext); this.RetryPolicy.NotifySuccess(executionContext); return; } catch (Exception exception) { shouldRetry = this.RetryPolicy.Retry(executionContext, exception); if (!shouldRetry) { LogForError(requestContext, exception); throw; } else { requestContext.Retries++; requestContext.Metrics.SetCounter(Metric.AttemptCount, requestContext.Retries); LogForRetry(requestContext, exception); } } PrepareForRetry(requestContext); using (requestContext.Metrics.StartEvent(Metric.RetryPauseTime)) this.RetryPolicy.WaitBeforeRetry(executionContext); } while (shouldRetry); } #if AWS_ASYNC_API /// /// Invokes the inner handler and performs a retry, if required as per the /// retry policy. /// /// The response type for the current request. /// The execution context, it contains the /// request and response context. /// A task that represents the asynchronous operation. public override async System.Threading.Tasks.Task InvokeAsync(IExecutionContext executionContext) { var requestContext = executionContext.RequestContext; var responseContext = executionContext.ResponseContext; bool shouldRetry = false; do { System.Runtime.ExceptionServices.ExceptionDispatchInfo capturedException = null; try { T result = await base.InvokeAsync(executionContext).ConfigureAwait(false); this.RetryPolicy.NotifySuccess(executionContext); return result; } catch (Exception e) { capturedException = System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(e); } if (capturedException != null) { shouldRetry = await this.RetryPolicy.RetryAsync(executionContext, capturedException.SourceException).ConfigureAwait(false); if (!shouldRetry) { LogForError(requestContext, capturedException.SourceException); capturedException.Throw(); } else { requestContext.Retries++; requestContext.Metrics.SetCounter(Metric.AttemptCount, requestContext.Retries); LogForRetry(requestContext, capturedException.SourceException); } } PrepareForRetry(requestContext); using (requestContext.Metrics.StartEvent(Metric.RetryPauseTime)) await RetryPolicy.WaitBeforeRetryAsync(executionContext).ConfigureAwait(false); } while (shouldRetry); throw new AmazonClientException("Neither a response was returned nor an exception was thrown in the Runtime RetryHandler."); } #endif #if AWS_APM_API /// /// Invokes the inner handler and performs a retry, if required as per the /// retry policy. /// /// The execution context which contains both the /// requests and response context. protected override void InvokeAsyncCallback(IAsyncExecutionContext executionContext) { var requestContext = executionContext.RequestContext; var responseContext = executionContext.ResponseContext; var exception = responseContext.AsyncResult.Exception; var syncExecutionContext = ExecutionContext.CreateFromAsyncContext(executionContext); if (exception != null) { var shouldRetry = this.RetryPolicy.Retry(syncExecutionContext, exception); if (shouldRetry) { requestContext.Retries++; requestContext.Metrics.SetCounter(Metric.AttemptCount, requestContext.Retries); LogForRetry(requestContext, exception); PrepareForRetry(requestContext); // Clear out current exception responseContext.AsyncResult.Exception = null; using (requestContext.Metrics.StartEvent(Metric.RetryPauseTime)) this.RetryPolicy.WaitBeforeRetry(syncExecutionContext); // Retry by calling InvokeAsync this.InvokeAsync(executionContext); return; } else { LogForError(requestContext, exception); } } else { this.RetryPolicy.NotifySuccess(syncExecutionContext); } // Call outer handler base.InvokeAsyncCallback(executionContext); } #endif /// /// Prepares the request for retry. /// /// Request context containing the state of the request. internal static void PrepareForRetry(IRequestContext requestContext) { if (requestContext.Request.ContentStream != null && requestContext.Request.OriginalStreamPosition >= 0) { var stream = requestContext.Request.ContentStream; // If the stream is wrapped in a HashStream, reset the HashStream var hashStream = stream as HashStream; if (hashStream != null) { hashStream.Reset(); stream = hashStream.GetSeekableBaseStream(); } stream.Position = requestContext.Request.OriginalStreamPosition; } } private void LogForRetry(IRequestContext requestContext, Exception exception) { #if !NETSTANDARD var webException = exception as WebException; if (webException != null) { Logger.InfoFormat("WebException ({1}) making request {2} to {3}. Attempting retry {4} of {5}.", webException.Status, requestContext.RequestName, requestContext.Request.Endpoint.ToString(), requestContext.Retries, this.RetryPolicy.MaxRetries); } else { #endif Logger.InfoFormat("{0} making request {1} to {2}. Attempting retry {3} of {4}.", exception.GetType().Name, requestContext.RequestName, requestContext.Request.Endpoint.ToString(), requestContext.Retries, this.RetryPolicy.MaxRetries); #if !NETSTANDARD } #endif } private void LogForError(IRequestContext requestContext, Exception exception) { Logger.Error(exception, "{0} making request {1} to {2}. Attempt {3}.", exception.GetType().Name, requestContext.RequestName, requestContext.Request.Endpoint.ToString(), requestContext.Retries + 1); } } }