Beispiel #1
0
        /// <summary>
        /// Executes an operation with retries.
        /// </summary>
        /// <param name="operationToAttempt">The operation, cannot be null.</param>
        /// <param name="errorDetectionStrategy">The error detection strategy to use instead of the default one.</param>
        /// <param name="notificationPolicy">The notification policy to use.</param>
        /// <exception cref="ArgumentNullException">If some of the non-nullable arguments are null.</exception>
        /// <exception cref="Exception">Any exception thrown by the <paramref name="operationToAttempt"/> after all the attempts have been exhausted.</exception>
        public void ExecuteWithRetries(Action operationToAttempt, ITransientErrorDetectionStrategy errorDetectionStrategy = null, IRetryNotificationPolicy notificationPolicy = null)
        {
            // Check the pre-conditions
            Guard.ArgumentNotNull(operationToAttempt, nameof(operationToAttempt));

            errorDetectionStrategy = errorDetectionStrategy ?? DefaultErrorDetectionStrategy;
            for (var attemptCount = 0; attemptCount < MaxAttempts; ++attemptCount)
            {
                try
                {
                    // Execute the operation
                    operationToAttempt();

                    // We are good to go
                    return;
                }
                catch (Exception ex)
                {
                    // Check if it is transient exception, we always treat timeout exceptions as transient
                    bool isTransient = IsTimeoutException(ex) || errorDetectionStrategy.IsTransientException(ex);
                    bool shouldRetry = ShouldRetry(attemptCount, isTransient);
                    // Notify the caller if we are requested to do so
                    if (notificationPolicy != null)
                    {
                        notificationPolicy.OnException(shouldRetry, ex);
                    }

                    if (!shouldRetry)
                    {
                        throw;
                    }
                }
                if (!UseFastRetriesForTesting)
                {
                    // Compute the delay and wait
                    var delay = WaitingPolicy.ComputeWaitTime(attemptCount);
                    Thread.Sleep(delay);
                }
            } // for
            throw new InvalidOperationException("This cannot be reached");
        }
Beispiel #2
0
        /// <summary>
        /// Executes an async operation (action with no result) with retries.
        /// </summary>
        /// <param name="operationToAttempt">The operation, cannot be null.</param>
        /// <param name="timeout">Optional timeout for the operation for single retry iteration, can be TimeSpan.Zero or Timeout.Infinite which means no timeout</param>
        /// <param name="cancellationToken">Optional token to cancel the waiting policy, operation and notification.</param>
        /// <param name="errorDetectionStrategy">Optional error detection strategy to use instead of the default one.</param>
        /// <param name="notificationPolicy">Optional notification policy to use.</param>
        /// <returns>The future for the result of the operation.</returns>
        /// <exception cref="ArgumentNullException">If some of the non-nullable arguments are null.</exception>
        /// <exception cref="TimeoutException">If the operation was timeout in all retries.</exception>
        /// <exception cref="OperationCanceledException">If the operation was cancelled.</exception>
        /// <exception cref="Exception">Any exception thrown by the <paramref name="operationToAttempt"/> after all the attempts have been exhausted.</exception>
        public async Task ExecuteWithRetriesAsync(
            Func <CancellationToken, Task> operationToAttempt,
            TimeSpan timeout = default(TimeSpan),
            CancellationToken cancellationToken = default(CancellationToken),
            ITransientErrorDetectionStrategy errorDetectionStrategy = null,
            IRetryNotificationPolicyAsync notificationPolicy        = null)
        {
            // Check the pre-conditions
            Guard.ArgumentNotNull(operationToAttempt, nameof(operationToAttempt));
            Guard.ArgumentNotNegativeValue(timeout.Ticks, nameof(timeout));

            errorDetectionStrategy = errorDetectionStrategy ?? DefaultErrorDetectionStrategy;
            for (var attemptCount = 0; attemptCount < MaxAttempts; ++attemptCount)
            {
                cancellationToken.ThrowIfCancellationRequested();

                // Construct timeout and cancellation linked token - it's important to timely Dispose
                // the "timeout" cancellation source (as well), because internally, it captures the
                // (current) thread's (CLR) execution context (synchronization context, logical call
                // context, etc.), so everything "tied" to these structures (like logging context, for
                // example, "chained" to a CallContext slot) would have a strong reference and will not
                // be eligible for garbage collection (note that these are managed resources!). These
                // would be released eventually when the timeout expires, but we should not rely on that
                // (if timeout passed in is considerable and the service is under pressure, we may easily
                // run out of memory).
                using (var timeoutCts = !IsNoTimeout(timeout) ? new CancellationTokenSource(timeout) : null)
                {
                    using (var timeoutAndCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(
                               cancellationToken, timeoutCts == null ? CancellationToken.None : timeoutCts.Token))
                    {
                        var timeoutAndCancellationToken = timeoutAndCancellationSource.Token;

                        try
                        {
                            // Execute the operation
                            await operationToAttempt(timeoutAndCancellationToken).ConfigureAwait(false);

                            // We are good to go
                            return;
                        }
                        catch (Exception ex)
                        {
                            // cancellationToken is set should bail out
                            cancellationToken.ThrowIfCancellationRequested();

                            // Check if it is transient exception, we always treat timeout exceptions as transient
                            bool isTransient = IsTimeoutException(ex) || errorDetectionStrategy.IsTransientException(ex);
                            bool shouldRetry = ShouldRetry(attemptCount, isTransient);
                            // Notify the caller if we are requested to do so
                            if (notificationPolicy != null)
                            {
                                await notificationPolicy.OnExceptionAsync(shouldRetry, ex, cancellationToken);
                            }

                            if (!shouldRetry)
                            {
                                if (ex is OperationCanceledException) // We wrap OperationCanceledException/TaskCanceledException in TimeoutException to indicate that operation was timed out
                                {
                                    throw new TimeoutException($"The operation has timed out after all {MaxAttempts} attempts. Each attempt took more than ${timeout.Milliseconds}ms.", ex);
                                }
                                else
                                {
                                    throw;
                                }
                            }
                        }
                    } // using
                }     // using

                if (!UseFastRetriesForTesting)
                {
                    // Compute the delay and wait
                    var delay = WaitingPolicy.ComputeWaitTime(attemptCount);

                    await Task.Delay(delay, cancellationToken).ConfigureAwait(false);
                }
            } // for
            throw new InvalidOperationException("This cannot be reached");
        }