/// <summary>
        /// Determines whether the operation should be retried and the interval until the next retry.
        /// </summary>
        /// <param name="retryContext">A <see cref="RetryContext"/> object that indicates the number of retries, the results of the last request, and whether the next retry should happen in the primary or secondary location, and specifies the location mode.</param>
        /// <param name="operationContext">An <see cref="OperationContext"/> object that represents the context for the current operation.</param>
        /// <returns>A <see cref="RetryInfo"/> object that indicates the location mode, and whether the next retry should happen in the primary or secondary location. If <c>null</c>, the operation will not be retried.</returns>
        public RetryInfo Evaluate(RetryContext retryContext, OperationContext operationContext)
        {
            CommonUtility.AssertNotNull("retryContext", retryContext);

            // Retry interval of a request to a location must take the time spent sending requests
            // to other locations into account. For example, assume a request was sent to the primary
            // location first, then to the secondary, and then to the primary again. If it
            // was supposed to wait 10 seconds between requests to the primary and the request to
            // the secondary took 3 seconds in total, retry interval should only be 7 seconds, because
            // in total, the requests will be 10 seconds apart from the primary locations' point of view.
            // For this calculation, current instance of the retry policy stores timestamp of the last
            // request to a specific location.
            if (retryContext.LastRequestResult.TargetLocation == StorageLocation.Primary)
            {
                this.lastPrimaryAttempt = retryContext.LastRequestResult.EndTime;
            }
            else
            {
                this.lastSecondaryAttempt = retryContext.LastRequestResult.EndTime;
            }

            // If a request sent to the secondary location fails with 404 (Not Found), it is possible
            // that the resource replication is not finished yet. So, in case of 404 only in the secondary
            // location, the failure should still be retryable.
            bool secondaryNotFound = (retryContext.LastRequestResult.TargetLocation == StorageLocation.Secondary) && (retryContext.LastRequestResult.HttpStatusCode == (int)HttpStatusCode.NotFound);

            TimeSpan retryInterval;
            if (this.ShouldRetry(retryContext.CurrentRetryCount, secondaryNotFound ? 500 : retryContext.LastRequestResult.HttpStatusCode, retryContext.LastRequestResult.Exception, out retryInterval, operationContext))
            {
                RetryInfo retryInfo = new RetryInfo(retryContext);

                // Moreover, in case of 404 when trying the secondary location, instead of retrying on the
                // secondary, further requests should be sent only to the primary location, as it most
                // probably has a higher chance of succeeding there.
                if (secondaryNotFound && (retryContext.LocationMode != LocationMode.SecondaryOnly))
                {
                    retryInfo.UpdatedLocationMode = LocationMode.PrimaryOnly;
                    retryInfo.TargetLocation = StorageLocation.Primary;
                }

                // Now is the time to calculate the exact retry interval. ShouldRetry call above already
                // returned back how long two requests to the same location should be apart from each other.
                // However, for the reasons explained above, the time spent between the last attempt to
                // the target location and current time must be subtracted from the total retry interval
                // that ShouldRetry returned.
                DateTimeOffset? lastAttemptTime = retryInfo.TargetLocation == StorageLocation.Primary ? this.lastPrimaryAttempt : this.lastSecondaryAttempt;
                if (lastAttemptTime.HasValue)
                {
                    TimeSpan sinceLastAttempt = CommonUtility.MaxTimeSpan(DateTimeOffset.Now - lastAttemptTime.Value, TimeSpan.Zero);
                    retryInfo.RetryInterval = retryInterval - sinceLastAttempt;
                }
                else
                {
                    retryInfo.RetryInterval = TimeSpan.Zero;
                }

                return retryInfo;
            }

            return null;
        }
Example #2
0
        /// <summary>
        /// Determines whether the operation should be retried and the interval until the next retry.
        /// </summary>
        /// <param name="retryContext">A <see cref="RetryContext"/> object that indicates the number of retries, the results of the last request, and whether the next retry should happen in the primary or secondary location, and specifies the location mode.</param>
        /// <param name="operationContext">An <see cref="OperationContext"/> object that represents the context for the current operation.</param>
        /// <returns>A <see cref="RetryInfo"/> object that indicates the location mode, and whether the next retry should happen in the primary or secondary location. If <c>null</c>, the operation will not be retried.</returns>
        public RetryInfo Evaluate(RetryContext retryContext, OperationContext operationContext)
        {
            CommonUtility.AssertNotNull("retryContext", retryContext);

            // Retry interval of a request to a location must take the time spent sending requests
            // to other locations into account. For example, assume a request was sent to the primary
            // location first, then to the secondary, and then to the primary again. If it
            // was supposed to wait 10 seconds between requests to the primary and the request to
            // the secondary took 3 seconds in total, retry interval should only be 7 seconds, because
            // in total, the requests will be 10 seconds apart from the primary locations' point of view.
            // For this calculation, current instance of the retry policy stores timestamp of the last
            // request to a specific location.
            if (retryContext.LastRequestResult.TargetLocation == StorageLocation.Primary)
            {
                this.lastPrimaryAttempt = retryContext.LastRequestResult.EndTime;
            }
            else
            {
                this.lastSecondaryAttempt = retryContext.LastRequestResult.EndTime;
            }

            // If a request sent to the secondary location fails with 404 (Not Found), it is possible
            // that the resource replication is not finished yet. So, in case of 404 only in the secondary
            // location, the failure should still be retryable.
            bool secondaryNotFound = (retryContext.LastRequestResult.TargetLocation == StorageLocation.Secondary) && (retryContext.LastRequestResult.HttpStatusCode == (int)HttpStatusCode.NotFound);

            TimeSpan retryInterval;

            if (this.ShouldRetry(retryContext.CurrentRetryCount, secondaryNotFound ? 500 : retryContext.LastRequestResult.HttpStatusCode, retryContext.LastRequestResult.Exception, out retryInterval, operationContext))
            {
                RetryInfo retryInfo = new RetryInfo(retryContext);

                // Moreover, in case of 404 when trying the secondary location, instead of retrying on the
                // secondary, further requests should be sent only to the primary location, as it most
                // probably has a higher chance of succeeding there.
                if (secondaryNotFound && (retryContext.LocationMode != LocationMode.SecondaryOnly))
                {
                    retryInfo.UpdatedLocationMode = LocationMode.PrimaryOnly;
                    retryInfo.TargetLocation      = StorageLocation.Primary;
                }

                // Now is the time to calculate the exact retry interval. ShouldRetry call above already
                // returned back how long two requests to the same location should be apart from each other.
                // However, for the reasons explained above, the time spent between the last attempt to
                // the target location and current time must be subtracted from the total retry interval
                // that ShouldRetry returned.
                DateTimeOffset?lastAttemptTime = retryInfo.TargetLocation == StorageLocation.Primary ? this.lastPrimaryAttempt : this.lastSecondaryAttempt;
                if (lastAttemptTime.HasValue)
                {
                    TimeSpan sinceLastAttempt = CommonUtility.MaxTimeSpan(DateTimeOffset.Now - lastAttemptTime.Value, TimeSpan.Zero);
                    retryInfo.RetryInterval = retryInterval - sinceLastAttempt;
                }
                else
                {
                    retryInfo.RetryInterval = TimeSpan.Zero;
                }

                return(retryInfo);
            }

            return(null);
        }