< Summary

Class:Azure.Core.Pipeline.RetryPolicy
Assembly:Azure.Core
File(s):C:\Git\azure-sdk-for-net\sdk\core\Azure.Core\src\Pipeline\Internal\RetryPolicy.cs
Covered lines:78
Uncovered lines:1
Coverable lines:79
Total lines:215
Line coverage:98.7% (78 of 79)
Covered branches:46
Total branches:48
Branch coverage:95.8% (46 of 48)

Metrics

MethodCyclomatic complexity Line coverage Branch coverage
.ctor(...)-100%100%
Process(...)-100%100%
ProcessAsync(...)-100%100%
ProcessAsync()-100%96.43%
WaitAsync()-100%100%
Wait(...)-100%100%
GetServerDelay(...)-91.67%92.86%
GetDelay(...)-100%100%
GetDelay(...)-100%100%
CalculateExponentialDelay(...)-100%100%

File(s)

C:\Git\azure-sdk-for-net\sdk\core\Azure.Core\src\Pipeline\Internal\RetryPolicy.cs

#LineLine coverage
 1// Copyright (c) Microsoft Corporation. All rights reserved.
 2// Licensed under the MIT License.
 3
 4using System;
 5using System.Collections.Generic;
 6using System.Diagnostics;
 7using System.Runtime.ExceptionServices;
 8using System.Threading;
 9using System.Threading.Tasks;
 10using Azure.Core.Diagnostics;
 11
 12namespace Azure.Core.Pipeline
 13{
 14    internal class RetryPolicy : HttpPipelinePolicy
 15    {
 16        private readonly RetryMode _mode;
 17        private readonly TimeSpan _delay;
 18        private readonly TimeSpan _maxDelay;
 19        private readonly int _maxRetries;
 20
 21021        private readonly Random _random = new ThreadSafeRandom();
 22
 21023        public RetryPolicy(RetryMode mode, TimeSpan delay, TimeSpan maxDelay, int maxRetries)
 24        {
 21025            _mode = mode;
 21026            _delay = delay;
 21027            _maxDelay = maxDelay;
 21028            _maxRetries = maxRetries;
 21029        }
 30
 31        private const string RetryAfterHeaderName = "Retry-After";
 32        private const string RetryAfterMsHeaderName = "retry-after-ms";
 33        private const string XRetryAfterMsHeaderName = "x-ms-retry-after-ms";
 34
 35        public override void Process(HttpMessage message, ReadOnlyMemory<HttpPipelinePolicy> pipeline)
 36        {
 88837            ProcessAsync(message, pipeline, false).EnsureCompleted();
 87438        }
 39
 40        public override ValueTask ProcessAsync(HttpMessage message, ReadOnlyMemory<HttpPipelinePolicy> pipeline)
 41        {
 90242            return ProcessAsync(message, pipeline, true);
 43        }
 44
 45        private async ValueTask ProcessAsync(HttpMessage message, ReadOnlyMemory<HttpPipelinePolicy> pipeline, bool asyn
 46        {
 179047            int attempt = 0;
 179048            List<Exception>? exceptions = null;
 64049            while (true)
 50            {
 243051                Exception? lastException = null;
 243052                var before = Stopwatch.GetTimestamp();
 53                try
 54                {
 243055                    if (async)
 56                    {
 122657                        await ProcessNextAsync(message, pipeline).ConfigureAwait(false);
 58                    }
 59                    else
 60                    {
 120461                        ProcessNext(message, pipeline);
 62                    }
 235063                }
 8064                catch (Exception ex)
 65                {
 8066                    if (exceptions == null)
 67                    {
 4868                        exceptions = new List<Exception>();
 69                    }
 70
 8071                    exceptions.Add(ex);
 72
 8073                    lastException = ex;
 8074                }
 75
 243076                var after = Stopwatch.GetTimestamp();
 243077                double elapsed = (after - before) / (double)Stopwatch.Frequency;
 78
 79                TimeSpan delay;
 80
 243081                attempt++;
 82
 243083                var shouldRetry = attempt <= _maxRetries;
 84
 243085                if (lastException != null)
 86                {
 8087                    if (shouldRetry && message.ResponseClassifier.IsRetriable(message, lastException))
 88                    {
 5289                        GetDelay(attempt, out delay);
 90                    }
 91                    else
 92                    {
 93                        // Rethrow a singular exception
 2894                        if (exceptions?.Count == 1)
 95                        {
 1296                            ExceptionDispatchInfo.Capture(lastException).Throw();
 97                        }
 98
 1699                        throw new AggregateException($"Retry failed after {attempt} tries.", exceptions);
 100                    }
 101                }
 2350102                else if (message.ResponseClassifier.IsErrorResponse(message))
 103                {
 700104                    if (shouldRetry && message.ResponseClassifier.IsRetriableResponse(message))
 105                    {
 588106                        GetDelay(message, attempt, out delay);
 107                    }
 108                    else
 109                    {
 110                        return;
 111                    }
 112                }
 113                else
 114                {
 1762115                    return;
 116                }
 117
 640118                if (delay > TimeSpan.Zero)
 119                {
 632120                    if (async)
 121                    {
 316122                        await WaitAsync(delay, message.CancellationToken).ConfigureAwait(false);
 123                    }
 124                    else
 125                    {
 316126                        Wait(delay, message.CancellationToken);
 127                    }
 128                }
 129
 640130                if (message.HasResponse)
 131                {
 132                    // Dispose the content stream to free up a connection if the request has any
 592133                    message.Response.ContentStream?.Dispose();
 134                }
 135
 640136                AzureCoreEventSource.Singleton.RequestRetrying(message.Request.ClientRequestId, attempt, elapsed);
 640137            }
 1762138        }
 139
 140        internal virtual async Task WaitAsync(TimeSpan time, CancellationToken cancellationToken)
 141        {
 204142            await Task.Delay(time, cancellationToken).ConfigureAwait(false);
 204143        }
 144
 145        internal virtual void Wait(TimeSpan time, CancellationToken cancellationToken)
 146        {
 204147            cancellationToken.WaitHandle.WaitOne(time);
 204148        }
 149
 150        protected virtual TimeSpan GetServerDelay(HttpMessage message)
 151        {
 588152            if (message.Response == null)
 153            {
 0154                return TimeSpan.Zero;
 155            }
 156
 588157            if (message.Response.TryGetHeader(RetryAfterMsHeaderName, out var retryAfterValue) ||
 588158                message.Response.TryGetHeader(XRetryAfterMsHeaderName, out retryAfterValue))
 159            {
 32160                if (int.TryParse(retryAfterValue, out var delaySeconds))
 161                {
 32162                    return TimeSpan.FromMilliseconds(delaySeconds);
 163                }
 164            }
 165
 556166            if (message.Response.TryGetHeader(RetryAfterHeaderName, out retryAfterValue))
 167            {
 44168                if (int.TryParse(retryAfterValue, out var delaySeconds))
 169                {
 28170                    return TimeSpan.FromSeconds(delaySeconds);
 171                }
 16172                if (DateTimeOffset.TryParse(retryAfterValue, out DateTimeOffset delayTime))
 173                {
 8174                    return delayTime - DateTimeOffset.Now;
 175                }
 176            }
 177
 520178            return TimeSpan.Zero;
 179        }
 180
 181        private void GetDelay(HttpMessage message, int attempted, out TimeSpan delay)
 182        {
 588183            delay = TimeSpan.Zero;
 184
 588185            switch (_mode)
 186            {
 187                case RetryMode.Fixed:
 76188                    delay = _delay;
 76189                    break;
 190                case RetryMode.Exponential:
 512191                    delay = CalculateExponentialDelay(attempted);
 192                    break;
 193            }
 194
 588195            TimeSpan serverDelay = GetServerDelay(message);
 588196            if (serverDelay > delay)
 197            {
 60198                delay = serverDelay;
 199            }
 588200        }
 201
 202        private void GetDelay(int attempted, out TimeSpan delay)
 203        {
 52204            delay = CalculateExponentialDelay(attempted);
 52205        }
 206
 207        private TimeSpan CalculateExponentialDelay(int attempted)
 208        {
 564209            return TimeSpan.FromMilliseconds(
 564210                Math.Min(
 564211                    (1 << (attempted - 1)) * _random.Next((int)(_delay.TotalMilliseconds * 0.8), (int)(_delay.TotalMilli
 564212                    _maxDelay.TotalMilliseconds));
 213        }
 214    }
 215}