< Summary

Class:Azure.Messaging.ServiceBus.ServiceBusRetryPolicy
Assembly:Azure.Messaging.ServiceBus
File(s):C:\Git\azure-sdk-for-net\sdk\servicebus\Azure.Messaging.ServiceBus\src\Primitives\ServiceBusRetryPolicy.cs
Covered lines:18
Uncovered lines:36
Coverable lines:54
Total lines:216
Line coverage:33.3% (18 of 54)
Covered branches:13
Total branches:26
Branch coverage:50% (13 of 26)

Metrics

MethodCyclomatic complexity Line coverage Branch coverage
.cctor()-0%100%
.ctor()-100%100%
get_IsServerBusy()-0%100%
get_ServerBusyExceptionMessage()-0%100%
get_Logger()-100%100%
Equals(...)-0%100%
GetHashCode()-0%100%
ToString()-0%100%
RunOperation()-69.57%81.25%
SetServerBusy(...)-0%0%
ResetServerBusy()-0%0%
ScheduleResetServerBusy()-0%100%

File(s)

C:\Git\azure-sdk-for-net\sdk\servicebus\Azure.Messaging.ServiceBus\src\Primitives\ServiceBusRetryPolicy.cs

#LineLine coverage
 1// Copyright (c) Microsoft Corporation. All rights reserved.
 2// Licensed under the MIT License.
 3
 4using System;
 5using System.ComponentModel;
 6using System.Runtime.ExceptionServices;
 7using System.Threading;
 8using System.Threading.Tasks;
 9using Azure.Messaging.ServiceBus.Amqp;
 10using Azure.Messaging.ServiceBus.Core;
 11using Azure.Messaging.ServiceBus.Diagnostics;
 12
 13namespace Azure.Messaging.ServiceBus
 14{
 15    /// <summary>
 16    ///   An abstract representation of a policy to govern retrying of messaging operations.
 17    /// </summary>
 18    ///
 19    /// <remarks>
 20    ///   It is recommended that developers without advanced needs not implement custom retry
 21    ///   policies but instead configure the default policy by specifying the desired set of
 22    ///   retry options when creating one of the Service Bus clients.
 23    /// </remarks>
 24    ///
 25    /// <seealso cref="ServiceBusRetryOptions"/>
 26    ///
 27    public abstract class ServiceBusRetryPolicy
 28    {
 29
 030        private static readonly TimeSpan ServerBusyBaseSleepTime = TimeSpan.FromSeconds(10);
 31
 11632        private readonly object serverBusyLock = new object();
 33
 34        // This is a volatile copy of IsServerBusy. IsServerBusy is synchronized with a lock, whereas encounteredServerB
 35        private volatile bool encounteredServerBusy;
 36
 37        /// <summary>
 38        /// Determines whether or not the server returned a busy error.
 39        /// </summary>
 040        private bool IsServerBusy { get; set; }
 41
 42        /// <summary>
 43        /// Gets the exception message when a server busy error is returned.
 44        /// </summary>
 045        private string ServerBusyExceptionMessage { get; set; }
 46
 47        /// <summary>
 48        ///   The instance of <see cref="ServiceBusEventSource" /> which can be mocked for testing.
 49        /// </summary>
 50        ///
 13851        internal ServiceBusEventSource Logger { get; set; } = ServiceBusEventSource.Log;
 52
 53        /// <summary>
 54        ///   Calculates the amount of time to allow the current attempt for an operation to
 55        ///   complete before considering it to be timed out.
 56        /// </summary>
 57        ///
 58        /// <param name="attemptCount">The number of total attempts that have been made, including the initial attempt b
 59        ///
 60        /// <returns>The amount of time to allow for an operation to complete.</returns>
 61        ///
 62        public abstract TimeSpan CalculateTryTimeout(int attemptCount);
 63
 64        /// <summary>
 65        ///   Calculates the amount of time to wait before another attempt should be made.
 66        /// </summary>
 67        ///
 68        /// <param name="lastException">The last exception that was observed for the operation to be retried.</param>
 69        /// <param name="attemptCount">The number of total attempts that have been made, including the initial attempt b
 70        ///
 71        /// <returns>The amount of time to delay before retrying the associated operation; if <c>null</c>, then the oper
 72        ///
 73        public abstract TimeSpan? CalculateRetryDelay(
 74            Exception lastException,
 75            int attemptCount);
 76
 77        /// <summary>
 78        ///   Determines whether the specified <see cref="System.Object" /> is equal to this instance.
 79        /// </summary>
 80        ///
 81        /// <param name="obj">The <see cref="System.Object" /> to compare with this instance.</param>
 82        ///
 83        /// <returns><c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>
 84        ///
 85        [EditorBrowsable(EditorBrowsableState.Never)]
 086        public override bool Equals(object obj) => base.Equals(obj);
 87
 88        /// <summary>
 89        ///   Returns a hash code for this instance.
 90        /// </summary>
 91        ///
 92        /// <returns>A hash code for this instance, suitable for use in hashing algorithms and data structures like a ha
 93        ///
 94        [EditorBrowsable(EditorBrowsableState.Never)]
 095        public override int GetHashCode() => base.GetHashCode();
 96
 97        /// <summary>
 98        ///   Converts the instance to string representation.
 99        /// </summary>
 100        ///
 101        /// <returns>A <see cref="System.String" /> that represents this instance.</returns>
 102        ///
 103        [EditorBrowsable(EditorBrowsableState.Never)]
 0104        public override string ToString() => base.ToString();
 105
 106        /// <summary>
 107        ///
 108        /// </summary>
 109        /// <param name="operation"></param>
 110        /// <param name="scope"></param>
 111        /// <param name="cancellationToken"></param>
 112        /// <returns></returns>
 113        internal async Task RunOperation(
 114            Func<TimeSpan, Task> operation,
 115            TransportConnectionScope scope,
 116            CancellationToken cancellationToken)
 117        {
 20118            var failedAttemptCount = 0;
 119
 120
 20121            TimeSpan tryTimeout = CalculateTryTimeout(0);
 20122            if (IsServerBusy && tryTimeout < ServerBusyBaseSleepTime)
 123            {
 124                // We are in a server busy state before we start processing.
 125                // Since ServerBusyBaseSleepTime > remaining time for the operation, we don't wait for the entire Sleep 
 0126                await Task.Delay(tryTimeout, cancellationToken).ConfigureAwait(false);
 0127                throw new ServiceBusException(
 0128                    ServerBusyExceptionMessage,
 0129                    ServiceBusFailureReason.ServiceBusy);
 130            }
 38131            while (!cancellationToken.IsCancellationRequested)
 132            {
 36133                if (IsServerBusy)
 134                {
 0135                    await Task.Delay(ServerBusyBaseSleepTime, cancellationToken).ConfigureAwait(false);
 136                }
 137
 138                try
 139                {
 36140                    await operation(tryTimeout).ConfigureAwait(false);
 0141                    return;
 142                }
 143
 144                catch (Exception ex)
 145                {
 36146                    Exception activeEx = AmqpExceptionHelper.TranslateException(ex);
 147
 148                    // Determine if there should be a retry for the next attempt; if so enforce the delay but do not qui
 149                    // Otherwise, throw the translated exception.
 150
 36151                    ++failedAttemptCount;
 36152                    TimeSpan? retryDelay = CalculateRetryDelay(activeEx, failedAttemptCount);
 36153                    if (retryDelay.HasValue && !scope.IsDisposed && !cancellationToken.IsCancellationRequested)
 154                    {
 18155                        Logger.RunOperationExceptionEncountered(activeEx.ToString());
 18156                        await Task.Delay(retryDelay.Value, cancellationToken).ConfigureAwait(false);
 18157                        tryTimeout = CalculateTryTimeout(failedAttemptCount);
 158                    }
 159                    else
 160                    {
 18161                        ExceptionDispatchInfo.Capture(activeEx)
 18162                            .Throw();
 163                    }
 164                }
 165            }
 166            // If no value has been returned nor exception thrown by this point,
 167            // then cancellation has been requested.
 2168            throw new TaskCanceledException();
 0169        }
 170
 171        internal void SetServerBusy(string exceptionMessage)
 172        {
 173            // multiple call to this method will not prolong the timer.
 0174            if (encounteredServerBusy)
 175            {
 0176                return;
 177            }
 178
 0179            lock (serverBusyLock)
 180            {
 0181                if (!encounteredServerBusy)
 182                {
 0183                    encounteredServerBusy = true;
 0184                    ServerBusyExceptionMessage = string.IsNullOrWhiteSpace(exceptionMessage) ?
 0185                        Resources.DefaultServerBusyException : exceptionMessage;
 0186                    IsServerBusy = true;
 0187                    _ = ScheduleResetServerBusy();
 188                }
 0189            }
 0190        }
 191
 192        internal void ResetServerBusy()
 193        {
 0194            if (!encounteredServerBusy)
 195            {
 0196                return;
 197            }
 198
 0199            lock (serverBusyLock)
 200            {
 0201                if (encounteredServerBusy)
 202                {
 0203                    encounteredServerBusy = false;
 0204                    ServerBusyExceptionMessage = Resources.DefaultServerBusyException;
 0205                    IsServerBusy = false;
 206                }
 0207            }
 0208        }
 209
 210        private async Task ScheduleResetServerBusy()
 211        {
 0212            await Task.Delay(ServerBusyBaseSleepTime).ConfigureAwait(false);
 0213            ResetServerBusy();
 0214        }
 215    }
 216}