From 9da3f7dcea4b559fd9ec68a53dc4601c8977469f Mon Sep 17 00:00:00 2001 From: reisenberger Date: Mon, 20 Nov 2017 23:38:44 +0000 Subject: [PATCH] WaitAndRetry/Forever: calc wait from error response (#367) WaitAndRetryForever: take error as input to sleepDurationProvider * Allow WaitAndRetryForever to take error as input to sleepDurationProvider. * Tidy up other WaitAndRetry overloads. * More tests. * Doco --- CHANGELOG.md | 1 + README.md | 2 + src/Polly.Net40Async.nuspec | 1 + .../Retry/RetryStateWaitAndRetryForever.cs | 13 +- .../RetryStateWaitAndRetryForeverAsync.cs | 9 +- src/Polly.Shared/Retry/RetrySyntax.cs | 70 +++----- src/Polly.Shared/Retry/RetrySyntaxAsync.cs | 70 +++----- src/Polly.Shared/Retry/RetryTResultSyntax.cs | 155 ++++++++++-------- .../Retry/RetryTResultSyntaxAsync.cs | 86 ++++------ .../Polly.SharedSpecs.projitems | 4 + .../Retry/WaitAndRetryAsyncSpecs.cs | 60 ++++--- .../Retry/WaitAndRetryForeverAsyncSpecs.cs | 36 ++++ .../Retry/WaitAndRetryForeverSpecs.cs | 30 +++- .../WaitAndRetryForeverTResultAsyncSpecs.cs | 61 +++++++ .../Retry/WaitAndRetryForeverTResultSpecs.cs | 56 +++++++ .../Retry/WaitAndRetrySpecs.cs | 55 ++++++- .../Retry/WaitAndRetryTResultAsyncSpecs.cs | 61 +++++++ .../Retry/WaitAndRetryTResultSpecs.cs | 56 +++++++ src/Polly.nuspec | 3 +- 19 files changed, 592 insertions(+), 237 deletions(-) create mode 100644 src/Polly.SharedSpecs/Retry/WaitAndRetryForeverTResultAsyncSpecs.cs create mode 100644 src/Polly.SharedSpecs/Retry/WaitAndRetryForeverTResultSpecs.cs create mode 100644 src/Polly.SharedSpecs/Retry/WaitAndRetryTResultAsyncSpecs.cs create mode 100644 src/Polly.SharedSpecs/Retry/WaitAndRetryTResultSpecs.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index a48c6ea87e4..247e6f65786 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## 5.6.0 - Bug fix: set context keys for generic execute method with PolicyWrap - Add GetPolicies extension method to IPolicyWrap +- Allow WaitAndRetry policies to calculate wait based on the handled fault ## 5.5.0 - Bug fix: non-generic CachePolicy with PolicyWrap diff --git a/README.md b/README.md index fd739702f75..dce633e0401 100644 --- a/README.md +++ b/README.md @@ -939,6 +939,8 @@ For details of changes by release see the [change log](https://github.com/App-vN * [@Extremo75](https://github.com/ExtRemo75) - Allow fallback delegates to take handled fault as input parameter. * [@reisenberger](https://github.com/reisenberger) and [@seanfarrow](https://github.com/SeanFarrow) - Add CachePolicy, with interfaces for pluggable cache providers and serializers. * [@MartinSStewart](https://github.com/martinsstewart) - Add GetPolicies extension method to IPolicyWrap. +* [@matst80](https://github.com/matst80) - Allow WaitAndRetry to take handled fault as an input to the sleepDurationProvider, allowing WaitAndRetry to take account of systems which specify a duration to wait as part of a fault response; eg Azure Cosmos DB may specify this in `x-ms-retry-after-ms` headers or in a property to an exception thrown by the Azure SDK. +* [@reisenberger](https://github.com/reisenberger) - Allow WaitAndRetryForever to take handled fault as an input to the sleepDurationProvider. # Sample Projects diff --git a/src/Polly.Net40Async.nuspec b/src/Polly.Net40Async.nuspec index 47267ebdc8e..19278f5420a 100644 --- a/src/Polly.Net40Async.nuspec +++ b/src/Polly.Net40Async.nuspec @@ -19,6 +19,7 @@ --------------------- - Bug fix: set context keys for generic execute method with PolicyWrap - Add GetPolicies extension method to IPolicyWrap + - Allow WaitAndRetry policies to calculate wait based on the handled fault 5.5.0 --------------------- diff --git a/src/Polly.Shared/Retry/RetryStateWaitAndRetryForever.cs b/src/Polly.Shared/Retry/RetryStateWaitAndRetryForever.cs index 091bb535460..2650fd4f5c1 100644 --- a/src/Polly.Shared/Retry/RetryStateWaitAndRetryForever.cs +++ b/src/Polly.Shared/Retry/RetryStateWaitAndRetryForever.cs @@ -7,11 +7,11 @@ namespace Polly.Retry internal partial class RetryStateWaitAndRetryForever : IRetryPolicyState { private int _errorCount; - private readonly Func _sleepDurationProvider; + private readonly Func, Context, TimeSpan> _sleepDurationProvider; private readonly Action, TimeSpan, Context> _onRetry; private readonly Context _context; - public RetryStateWaitAndRetryForever(Func sleepDurationProvider, Action, TimeSpan, Context> onRetry, Context context) + public RetryStateWaitAndRetryForever(Func, Context, TimeSpan> sleepDurationProvider, Action, TimeSpan, Context> onRetry, Context context) { _sleepDurationProvider = sleepDurationProvider; _onRetry = onRetry; @@ -23,12 +23,13 @@ public bool CanRetry(DelegateResult delegateResult, CancellationToken c if (_errorCount < int.MaxValue) { _errorCount += 1; - } + } - var currentTimeSpan = _sleepDurationProvider(_errorCount, _context); - _onRetry(delegateResult, currentTimeSpan, _context); + TimeSpan waitTimeSpan = _sleepDurationProvider(_errorCount, delegateResult, _context); - SystemClock.Sleep(currentTimeSpan, cancellationToken); + _onRetry(delegateResult, waitTimeSpan, _context); + + SystemClock.Sleep(waitTimeSpan, cancellationToken); return true; } diff --git a/src/Polly.Shared/Retry/RetryStateWaitAndRetryForeverAsync.cs b/src/Polly.Shared/Retry/RetryStateWaitAndRetryForeverAsync.cs index f47b15272cd..bd8793cb107 100644 --- a/src/Polly.Shared/Retry/RetryStateWaitAndRetryForeverAsync.cs +++ b/src/Polly.Shared/Retry/RetryStateWaitAndRetryForeverAsync.cs @@ -9,7 +9,7 @@ internal partial class RetryStateWaitAndRetryForever : IRetryPolicyStat { private readonly Func, TimeSpan, Context, Task> _onRetryAsync; - public RetryStateWaitAndRetryForever(Func sleepDurationProvider, Func, TimeSpan, Context, Task> onRetryAsync, Context context) + public RetryStateWaitAndRetryForever(Func, Context, TimeSpan> sleepDurationProvider, Func, TimeSpan, Context, Task> onRetryAsync, Context context) { _sleepDurationProvider = sleepDurationProvider; _onRetryAsync = onRetryAsync; @@ -23,10 +23,11 @@ public async Task CanRetryAsync(DelegateResult delegateResult, Ca _errorCount += 1; } - var currentTimeSpan = _sleepDurationProvider(_errorCount, _context); - await _onRetryAsync(delegateResult, currentTimeSpan, _context).ConfigureAwait(continueOnCapturedContext); + TimeSpan waitTimeSpan = _sleepDurationProvider(_errorCount, delegateResult, _context); - await SystemClock.SleepAsync(currentTimeSpan, cancellationToken).ConfigureAwait(continueOnCapturedContext); + await _onRetryAsync(delegateResult, waitTimeSpan, _context).ConfigureAwait(continueOnCapturedContext); + + await SystemClock.SleepAsync(waitTimeSpan, cancellationToken).ConfigureAwait(continueOnCapturedContext); return true; } diff --git a/src/Polly.Shared/Retry/RetrySyntax.cs b/src/Polly.Shared/Retry/RetrySyntax.cs index e9b68aa7161..2738e997415 100644 --- a/src/Polly.Shared/Retry/RetrySyntax.cs +++ b/src/Polly.Shared/Retry/RetrySyntax.cs @@ -306,46 +306,7 @@ public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int ret ); } - ///// - ///// Builds a that will wait and retry times - ///// calling on each retry with the raised exception, current sleep duration, retry count, and context data. - ///// On each retry, the duration to wait is calculated by calling with - ///// the current retry attempt allowing an exponentially increasing wait time (exponential backoff). - ///// - ///// The policy builder. - ///// The retry count. - ///// The function that provides the duration to wait for for a particular retry attempt. - ///// The action to call on each retry. - ///// The policy instance. - ///// retryCount;Value must be greater than or equal to zero. - ///// - ///// timeSpanProvider - ///// or - ///// onRetry - ///// - //public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action onRetry) - //{ - // if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); - // if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - // if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - // return new RetryPolicy( - // (action, context, cancellationToken) => RetryEngine.Implementation( - // (ctx, ct) => { action(ctx, ct); return EmptyStruct.Instance; }, - // context, - // cancellationToken, - // policyBuilder.ExceptionPredicates, - // PredicateHelper.EmptyResultPredicates, - // () => new RetryStateWaitAndRetryWithProvider( - // retryCount, - // sleepDurationProvider, - // (outcome, timespan, i, ctx) => onRetry(outcome.Exception, timespan, i, ctx), - // context) - // ), policyBuilder.ExceptionPredicates); - //} - - // For v560waitDurationFromErrorResponse: delete the overload above, and replace with the two below - + /// /// Builds a that will wait and retry times /// calling on each retry with the raised exception, current sleep duration, retry count, and context data. @@ -365,6 +326,7 @@ public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int ret /// public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action onRetry) { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); return policyBuilder.WaitAndRetry( retryCount, (i, outcome, ctx) => sleepDurationProvider(i, ctx), @@ -394,8 +356,6 @@ public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int ret if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - //var wrappedEmptyStruct = new Action, TimeSpan, int, Context>() - return new RetryPolicy( (action, context, cancellationToken) => RetryEngine.Implementation( (ctx, ct) => { action(ctx, ct); return EmptyStruct.Instance; }, @@ -562,17 +522,39 @@ public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, /// sleepDurationProvider /// onRetry public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + return policyBuilder.WaitAndRetryForever( + (i, outcome, ctx) => sleepDurationProvider(i, ctx), + onRetry + ); + } + + /// + /// Builds a that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the raised exception and + /// execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action onRetry) { if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); return new RetryPolicy((action, context, cancellationToken) => RetryEngine.Implementation( (ctx, ct) => { action(ctx, ct); return EmptyStruct.Instance; }, - context, + context, cancellationToken, policyBuilder.ExceptionPredicates, PredicateHelper.EmptyResultPredicates, - () => new RetryStateWaitAndRetryForever(sleepDurationProvider, (outcome, timespan, ctx) => onRetry(outcome.Exception, timespan, ctx), context) + () => new RetryStateWaitAndRetryForever( + (i, outcome, ctx) => sleepDurationProvider(i, outcome.Exception, ctx), + (outcome, timespan, ctx) => onRetry(outcome.Exception, timespan, ctx), context) ), policyBuilder.ExceptionPredicates); } } diff --git a/src/Polly.Shared/Retry/RetrySyntaxAsync.cs b/src/Polly.Shared/Retry/RetrySyntaxAsync.cs index 5f4b2219603..21f9459ee1d 100644 --- a/src/Polly.Shared/Retry/RetrySyntaxAsync.cs +++ b/src/Polly.Shared/Retry/RetrySyntaxAsync.cs @@ -565,46 +565,6 @@ public static RetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, in ); } - ///// - ///// Builds a that will wait and retry times - ///// calling on each retry with the raised exception, the current sleep duration, retry count, and context data. - ///// On each retry, the duration to wait is calculated by calling with - ///// the current retry attempt allowing an exponentially increasing wait time (exponential backoff). - ///// - ///// The policy builder. - ///// The retry count. - ///// The function that provides the duration to wait for for a particular retry attempt. - ///// The action to call asynchronously on each retry. - ///// The policy instance. - ///// retryCount;Value must be greater than or equal to zero. - ///// - ///// sleepDurationProvider - ///// or - ///// onRetryAsync - ///// - //public static RetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, - // Func sleepDurationProvider, Func onRetryAsync) - //{ - // if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); - // if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - // if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - // return new RetryPolicy( - // (action, context, cancellationToken, continueOnCapturedContext) => - // RetryEngine.ImplementationAsync( - // async (ctx, ct) => { await action(ctx, ct).ConfigureAwait(continueOnCapturedContext); return EmptyStruct.Instance; }, - // context, - // cancellationToken, - // policyBuilder.ExceptionPredicates, - // PredicateHelper.EmptyResultPredicates, - // () => new RetryStateWaitAndRetryWithProvider(retryCount, sleepDurationProvider, (outcome, timespan, i, ctx) => onRetryAsync(outcome.Exception, timespan, i, ctx), context), - // continueOnCapturedContext), - // policyBuilder.ExceptionPredicates - // ); - //} - - // For v560waitDurationFromErrorResponse: delete the overload above, and replace with the two below - /// /// Builds a that will wait and retry times /// calling on each retry with the raised exception, the current sleep duration, retry count, and context data. @@ -625,6 +585,7 @@ public static RetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, in public static RetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Func onRetryAsync) { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); return policyBuilder.WaitAndRetryAsync( retryCount, (i, outcome, ctx) => sleepDurationProvider(i, ctx), @@ -964,6 +925,26 @@ public static RetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuil /// sleepDurationProvider /// onRetryAsync public static RetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func onRetryAsync) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + return policyBuilder.WaitAndRetryForeverAsync( + (i, outcome, ctx) => sleepDurationProvider(i, ctx), + onRetryAsync + ); + } + + /// + /// Builds a that will wait and retry indefinitely + /// calling on each retry with the raised exception and + /// execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetryAsync + public static RetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func onRetryAsync) { if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); @@ -976,9 +957,12 @@ public static RetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuil cancellationToken, policyBuilder.ExceptionPredicates, PredicateHelper.EmptyResultPredicates, - () => new RetryStateWaitAndRetryForever(sleepDurationProvider, (outcome, timespan, ctx) => onRetryAsync(outcome.Exception, timespan, ctx), context), - continueOnCapturedContext - ), policyBuilder.ExceptionPredicates); + () => new RetryStateWaitAndRetryForever( + (i, outcome, ctx) => sleepDurationProvider(i, outcome.Exception, ctx), + (outcome, timespan, ctx) => onRetryAsync(outcome.Exception, timespan, ctx), + context), + continueOnCapturedContext + ), policyBuilder.ExceptionPredicates); } } } diff --git a/src/Polly.Shared/Retry/RetryTResultSyntax.cs b/src/Polly.Shared/Retry/RetryTResultSyntax.cs index 9bb7e9634e8..1ade32df69f 100644 --- a/src/Polly.Shared/Retry/RetryTResultSyntax.cs +++ b/src/Polly.Shared/Retry/RetryTResultSyntax.cs @@ -268,6 +268,76 @@ public static RetryPolicy WaitAndRetry(this PolicyBuilder + /// Builds a that will wait and retry times. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry attempt allowing an exponentially increasing wait time (exponential backoff). + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The policy instance. + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider) + { + Action, TimeSpan, int, Context> doNothing = (_, __, ___, ____) => { }; + + return policyBuilder.WaitAndRetry(retryCount, sleepDurationProvider, doNothing); + } + + /// + /// Builds a that will wait and retry times + /// calling on each retry with the handled exception or result, current sleep duration and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry attempt allowing an exponentially increasing wait time (exponential backoff). + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// sleepDurationProvider + /// or + /// onRetry + /// + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action, TimeSpan, Context> onRetry) + { + if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); + + return policyBuilder.WaitAndRetry( + retryCount, + sleepDurationProvider, + (outcome, span, i, ctx) => onRetry(outcome, span, ctx) + ); + } + + /// + /// Builds a that will wait and retry times + /// calling on each retry with the handled exception or result, current sleep duration, retry count, and context data. + /// On each retry, the duration to wait is calculated by calling with + /// the current retry attempt allowing an exponentially increasing wait time (exponential backoff). + /// + /// The policy builder. + /// The retry count. + /// The function that provides the duration to wait for for a particular retry attempt. + /// The action to call on each retry. + /// The policy instance. + /// retryCount;Value must be greater than or equal to zero. + /// + /// timeSpanProvider + /// or + /// onRetry + /// + public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action, TimeSpan, int, Context> onRetry) + { + return policyBuilder.WaitAndRetry( + retryCount, + (i, outcome, ctx) => sleepDurationProvider(i, ctx), + onRetry + ); + } + /// /// Builds a that will wait and retry times. /// On each retry, the duration to wait is calculated by calling with @@ -348,69 +418,6 @@ public static RetryPolicy WaitAndRetry(this PolicyBuilder - ///// Builds a that will wait and retry times - ///// calling on each retry with the handled exception or result, current sleep duration, retry count, and context data. - ///// On each retry, the duration to wait is calculated by calling with - ///// the current retry attempt allowing an exponentially increasing wait time (exponential backoff). - ///// - ///// The policy builder. - ///// The retry count. - ///// The function that provides the duration to wait for for a particular retry attempt. - ///// The action to call on each retry. - ///// The policy instance. - ///// retryCount;Value must be greater than or equal to zero. - ///// - ///// timeSpanProvider - ///// or - ///// onRetry - ///// - //public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action, TimeSpan, int, Context> onRetry) - //{ - // return policyBuilder.WaitAndRetry( - // retryCount, - // (i, outcome, ctx) => sleepDurationProvider(i, ctx), - // onRetry); - //} - - ///// - ///// Builds a that will wait and retry times - ///// calling on each retry with the handled exception or result, current sleep duration, retry count, and context data. - ///// On each retry, the duration to wait is calculated by calling with - ///// the current retry attempt allowing an exponentially increasing wait time (exponential backoff). - ///// - ///// The policy builder. - ///// The retry count. - ///// The function that provides the duration to wait for for a particular retry attempt. - ///// The action to call on each retry. - ///// The policy instance. - ///// retryCount;Value must be greater than or equal to zero. - ///// - ///// timeSpanProvider - ///// or - ///// onRetry - ///// - //public static RetryPolicy WaitAndRetry(this PolicyBuilder policyBuilder, int retryCount, Func, Context, TimeSpan> sleepDurationProvider, Action, TimeSpan, int, Context> onRetry) - //{ - // if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); - // if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - // if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); - - // return new RetryPolicy( - // (action, context, cancellationToken) => RetryEngine.Implementation( - // action, - // context, - // cancellationToken, - // policyBuilder.ExceptionPredicates, - // policyBuilder.ResultPredicates, - // () => new RetryStateWaitAndRetryWithProvider(retryCount, sleepDurationProvider, onRetry, context) - // ), - // policyBuilder.ExceptionPredicates, - // policyBuilder.ResultPredicates); - //} - /// /// Builds a that will wait and retry as many times as there are provided /// On each retry, the duration to wait is the current item. @@ -565,6 +572,26 @@ public static RetryPolicy WaitAndRetryForever(this PolicyBuild /// sleepDurationProvider /// onRetry public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Action, TimeSpan, Context> onRetry) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + return policyBuilder.WaitAndRetryForever( + (i, outcome, ctx) => sleepDurationProvider(i, ctx), + onRetry + ); + } + + /// + /// Builds a that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the handled exception or result and + /// execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetry + public static RetryPolicy WaitAndRetryForever(this PolicyBuilder policyBuilder, Func, Context, TimeSpan> sleepDurationProvider, Action, TimeSpan, Context> onRetry) { if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); @@ -577,7 +604,7 @@ public static RetryPolicy WaitAndRetryForever(this PolicyBuild policyBuilder.ExceptionPredicates, policyBuilder.ResultPredicates, () => new RetryStateWaitAndRetryForever(sleepDurationProvider, onRetry, context) - ), + ), policyBuilder.ExceptionPredicates, policyBuilder.ResultPredicates ); diff --git a/src/Polly.Shared/Retry/RetryTResultSyntaxAsync.cs b/src/Polly.Shared/Retry/RetryTResultSyntaxAsync.cs index c3ae0c8f26d..8b826288dfe 100644 --- a/src/Polly.Shared/Retry/RetryTResultSyntaxAsync.cs +++ b/src/Polly.Shared/Retry/RetryTResultSyntaxAsync.cs @@ -497,7 +497,7 @@ public static RetryPolicy WaitAndRetryAsync(this PolicyBuilder /// onRetry /// public static RetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, - Func, Context, TimeSpan> sleepDurationProvider, Action, TimeSpan, Context> onRetry) + Func sleepDurationProvider, Action, TimeSpan, Context> onRetry) { if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); @@ -527,7 +527,7 @@ public static RetryPolicy WaitAndRetryAsync(this PolicyBuilder /// or /// onRetryAsync /// - public static RetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func, Context, TimeSpan> sleepDurationProvider, Func, TimeSpan, Context, Task> onRetryAsync) + public static RetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Func, TimeSpan, Context, Task> onRetryAsync) { if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); @@ -555,7 +555,7 @@ public static RetryPolicy WaitAndRetryAsync(this PolicyBuilder /// or /// onRetry /// - public static RetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func, Context, TimeSpan> sleepDurationProvider, Action, TimeSpan, int, Context> onRetry) + public static RetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Action, TimeSpan, int, Context> onRetry) { if (onRetry == null) throw new ArgumentNullException(nameof(onRetry)); @@ -568,47 +568,6 @@ public static RetryPolicy WaitAndRetryAsync(this PolicyBuilder ); } - ///// - ///// Builds a that will wait and retry times - ///// calling on each retry with the handled exception or result, the current sleep duration, retry count, and context data. - ///// On each retry, the duration to wait is calculated by calling with - ///// the current retry attempt allowing an exponentially increasing wait time (exponential backoff). - ///// - ///// The policy builder. - ///// The retry count. - ///// The function that provides the duration to wait for for a particular retry attempt. - ///// The action to call asynchronously on each retry. - ///// The policy instance. - ///// retryCount;Value must be greater than or equal to zero. - ///// - ///// sleepDurationProvider - ///// or - ///// onRetryAsync - ///// - //public static RetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, - // Func, Context, TimeSpan> sleepDurationProvider, Func, TimeSpan, int, Context, Task> onRetryAsync) - //{ - // if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero."); - // if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); - // if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); - - // return new RetryPolicy( - // (action, context, cancellationToken, continueOnCapturedContext) => - // RetryEngine.ImplementationAsync( - // action, - // context, - // cancellationToken, - // policyBuilder.ExceptionPredicates, - // policyBuilder.ResultPredicates, - // () => new RetryStateWaitAndRetryWithProvider(retryCount, sleepDurationProvider, onRetryAsync, context), - // continueOnCapturedContext), - // policyBuilder.ExceptionPredicates, - // policyBuilder.ResultPredicates - // ); - //} - - // For v560waitDurationFromErrorResponse: delete the overload above, and replace with the two below - /// /// Builds a that will wait and retry times /// calling on each retry with the handled exception or result, the current sleep duration, retry count, and context data. @@ -629,6 +588,7 @@ public static RetryPolicy WaitAndRetryAsync(this PolicyBuilder public static RetryPolicy WaitAndRetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func sleepDurationProvider, Func, TimeSpan, int, Context, Task> onRetryAsync) { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); return policyBuilder.WaitAndRetryAsync( retryCount, (i, outcome, ctx) => sleepDurationProvider(i, ctx), @@ -965,21 +925,41 @@ public static RetryPolicy WaitAndRetryForeverAsync(this Policy /// sleepDurationProvider /// onRetryAsync public static RetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func sleepDurationProvider, Func, TimeSpan, Context, Task> onRetryAsync) + { + if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); + return policyBuilder.WaitAndRetryForeverAsync( + (i, outcome, ctx) => sleepDurationProvider(i, ctx), + onRetryAsync + ); + } + + /// + /// Builds a that will wait and retry indefinitely until the action succeeds, + /// calling on each retry with the handled exception or result and + /// execution context. + /// + /// The policy builder. + /// A function providing the duration to wait before retrying. + /// The action to call asynchronously on each retry. + /// The policy instance. + /// sleepDurationProvider + /// onRetryAsync + public static RetryPolicy WaitAndRetryForeverAsync(this PolicyBuilder policyBuilder, Func, Context, TimeSpan> sleepDurationProvider, Func, TimeSpan, Context, Task> onRetryAsync) { if (sleepDurationProvider == null) throw new ArgumentNullException(nameof(sleepDurationProvider)); if (onRetryAsync == null) throw new ArgumentNullException(nameof(onRetryAsync)); return new RetryPolicy( (action, context, cancellationToken, continueOnCapturedContext) => - RetryEngine.ImplementationAsync( - action, - context, - cancellationToken, - policyBuilder.ExceptionPredicates, - policyBuilder.ResultPredicates, - () => new RetryStateWaitAndRetryForever(sleepDurationProvider, onRetryAsync, context), - continueOnCapturedContext - ), + RetryEngine.ImplementationAsync( + action, + context, + cancellationToken, + policyBuilder.ExceptionPredicates, + policyBuilder.ResultPredicates, + () => new RetryStateWaitAndRetryForever(sleepDurationProvider, onRetryAsync, context), + continueOnCapturedContext + ), policyBuilder.ExceptionPredicates, policyBuilder.ResultPredicates); } diff --git a/src/Polly.SharedSpecs/Polly.SharedSpecs.projitems b/src/Polly.SharedSpecs/Polly.SharedSpecs.projitems index c67f8639a5e..7fc3a25557e 100644 --- a/src/Polly.SharedSpecs/Polly.SharedSpecs.projitems +++ b/src/Polly.SharedSpecs/Polly.SharedSpecs.projitems @@ -80,8 +80,12 @@ + + + + diff --git a/src/Polly.SharedSpecs/Retry/WaitAndRetryAsyncSpecs.cs b/src/Polly.SharedSpecs/Retry/WaitAndRetryAsyncSpecs.cs index 42817707789..542bd2eea80 100644 --- a/src/Polly.SharedSpecs/Retry/WaitAndRetryAsyncSpecs.cs +++ b/src/Polly.SharedSpecs/Retry/WaitAndRetryAsyncSpecs.cs @@ -561,42 +561,64 @@ public async Task Should_calculate_retry_timespans_from_current_retry_attempt_an } [Fact] - public async Task Should_calculate_retry_timespans_from_current_retry_attempt_and_timespan_provider_with_exception_handling() + public async Task Should_be_able_to_pass_handled_exception_to_sleepdurationprovider() { - var expectedRetryWaits = new[] - { - 2.Seconds(), - 4.Seconds(), - 8.Seconds(), - 16.Seconds(), - 32.Seconds() - }; - object capturedExceptionInstance = null; - DivideByZeroException exceptionInstance = new DivideByZeroException() { - - }; - - var actualRetryWaits = new List(); + DivideByZeroException exceptionInstance = new DivideByZeroException(); var policy = Policy .Handle() .WaitAndRetryAsync(5, - sleepDurationProvider:( retries, ex, ctx) => + sleepDurationProvider:( retries, ex, ctx) => { capturedExceptionInstance = ex; - return TimeSpan.FromMilliseconds(10); + return TimeSpan.FromMilliseconds(0); }, - onRetryAsync: ( ts, i, ctx, task) => + onRetryAsync: (ts, i, ctx, task) => { return TaskHelper.EmptyTask; } ); await policy.RaiseExceptionAsync(exceptionInstance); + capturedExceptionInstance.Should().Be(exceptionInstance); - + } + + [Fact] + public async Task Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() + { + Dictionary expectedRetryWaits = new Dictionary(){ + + {new DivideByZeroException(), 2.Seconds()}, + {new ArgumentNullException(), 4.Seconds()}, + }; + + var actualRetryWaits = new List(); + + var policy = Policy + .Handle() + .WaitAndRetryAsync(2, + sleepDurationProvider: (retryAttempt, exc, ctx) => + { + return expectedRetryWaits[exc]; + }, + onRetryAsync: (_, timeSpan, __, ___) => + { + actualRetryWaits.Add(timeSpan); + return TaskHelper.EmptyTask; + }); + + using (var enumerator = expectedRetryWaits.GetEnumerator()) + { + await policy.ExecuteAsync(() => { + if (enumerator.MoveNext()) throw enumerator.Current.Key; + return TaskHelper.EmptyTask; + }); + } + + actualRetryWaits.Should().ContainInOrder(expectedRetryWaits.Values); } [Fact] diff --git a/src/Polly.SharedSpecs/Retry/WaitAndRetryForeverAsyncSpecs.cs b/src/Polly.SharedSpecs/Retry/WaitAndRetryForeverAsyncSpecs.cs index b3a0ccd1123..17d8e2e7c4a 100644 --- a/src/Polly.SharedSpecs/Retry/WaitAndRetryForeverAsyncSpecs.cs +++ b/src/Polly.SharedSpecs/Retry/WaitAndRetryForeverAsyncSpecs.cs @@ -287,6 +287,42 @@ public async Task Should_calculate_retry_timespans_from_current_retry_attempt_an .ContainInOrder(expectedRetryWaits); } + [Fact] + public async Task Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() + { + Dictionary expectedRetryWaits = new Dictionary(){ + + {new DivideByZeroException(), 2.Seconds()}, + {new ArgumentNullException(), 4.Seconds()}, + }; + + var actualRetryWaits = new List(); + + var policy = Policy + .Handle() + .WaitAndRetryForeverAsync( + sleepDurationProvider: (retryAttempt, exc, ctx) => + { + return expectedRetryWaits[exc]; + }, + onRetryAsync: (_, timeSpan, __) => + { + actualRetryWaits.Add(timeSpan); + return TaskHelper.EmptyTask; + }); + + using (var enumerator = expectedRetryWaits.GetEnumerator()) + { + await policy.ExecuteAsync(() => { + if (enumerator.MoveNext()) throw enumerator.Current.Key; + return TaskHelper.EmptyTask; + }); + } + + actualRetryWaits.Should().ContainInOrder(expectedRetryWaits.Values); + } + + [Fact] public async Task Should_be_able_to_pass_retry_duration_from_execution_to_sleepDurationProvider_via_context() { diff --git a/src/Polly.SharedSpecs/Retry/WaitAndRetryForeverSpecs.cs b/src/Polly.SharedSpecs/Retry/WaitAndRetryForeverSpecs.cs index c7217615e3a..26530c38830 100644 --- a/src/Polly.SharedSpecs/Retry/WaitAndRetryForeverSpecs.cs +++ b/src/Polly.SharedSpecs/Retry/WaitAndRetryForeverSpecs.cs @@ -35,9 +35,11 @@ public void Should_throw_when_sleep_duration_provider_is_null_with_context() { Action onRetry = (_, __, ___) => { }; + Func sleepDurationProvider = null; + Action policy = () => Policy .Handle() - .WaitAndRetryForever(null, onRetry); + .WaitAndRetryForever(sleepDurationProvider, onRetry); policy.ShouldThrow().And .ParamName.Should().Be("sleepDurationProvider"); @@ -283,6 +285,32 @@ public void Should_calculate_retry_timespans_from_current_retry_attempt_and_time .ContainInOrder(expectedRetryWaits); } + [Fact] + public void Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() + { + Dictionary expectedRetryWaits = new Dictionary(){ + + {new DivideByZeroException(), 2.Seconds()}, + {new ArgumentNullException(), 4.Seconds()}, + }; + + var actualRetryWaits = new List(); + + var policy = Policy + .Handle() + .WaitAndRetryForever( + (retryAttempt, exc, ctx) => expectedRetryWaits[exc], + (_, timeSpan, __) => actualRetryWaits.Add(timeSpan) + ); + + using (var enumerator = expectedRetryWaits.GetEnumerator()) + { + policy.Execute(() => { if (enumerator.MoveNext()) throw enumerator.Current.Key; }); + } + + actualRetryWaits.Should().ContainInOrder(expectedRetryWaits.Values); + } + [Fact] public void Should_be_able_to_pass_retry_duration_from_execution_to_sleepDurationProvider_via_context() { diff --git a/src/Polly.SharedSpecs/Retry/WaitAndRetryForeverTResultAsyncSpecs.cs b/src/Polly.SharedSpecs/Retry/WaitAndRetryForeverTResultAsyncSpecs.cs new file mode 100644 index 00000000000..2937f38477b --- /dev/null +++ b/src/Polly.SharedSpecs/Retry/WaitAndRetryForeverTResultAsyncSpecs.cs @@ -0,0 +1,61 @@ +using System; +using System.Threading.Tasks; +using System.Collections.Generic; +using FluentAssertions; +using Polly.Specs.Helpers; +using Polly.Utilities; +using Xunit; + +namespace Polly.Specs.Retry +{ + [Collection("SystemClockDependantCollection")] + public class WaitAndRetryForeverTResultAsyncSpecs : IDisposable + { + public WaitAndRetryForeverTResultAsyncSpecs() + { + // do nothing on call to sleep + SystemClock.SleepAsync = (_, __) => TaskHelper.EmptyTask; + } + + [Fact] + public async Task Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() + { + Dictionary expectedRetryWaits = new Dictionary(){ + + {ResultPrimitive.Fault, 2.Seconds()}, + {ResultPrimitive.FaultAgain, 4.Seconds()}, + }; + + var actualRetryWaits = new List(); + + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .WaitAndRetryForeverAsync( + (retryAttempt, outcome, ctx) => expectedRetryWaits[outcome.Result], + (_, timeSpan, __) => + { + actualRetryWaits.Add(timeSpan); + return TaskHelper.EmptyTask; + }); + + using (var enumerator = expectedRetryWaits.GetEnumerator()) + { + await policy.ExecuteAsync(async () => + { + await TaskHelper.EmptyTask; + if (enumerator.MoveNext()) return enumerator.Current.Key; + else return ResultPrimitive.Undefined; + }); + } + + actualRetryWaits.Should().ContainInOrder(expectedRetryWaits.Values); + } + + public void Dispose() + { + SystemClock.Reset(); + } + + } +} \ No newline at end of file diff --git a/src/Polly.SharedSpecs/Retry/WaitAndRetryForeverTResultSpecs.cs b/src/Polly.SharedSpecs/Retry/WaitAndRetryForeverTResultSpecs.cs new file mode 100644 index 00000000000..94366a3ccf7 --- /dev/null +++ b/src/Polly.SharedSpecs/Retry/WaitAndRetryForeverTResultSpecs.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using FluentAssertions; +using Polly.Specs.Helpers; +using Polly.Utilities; +using Xunit; + +namespace Polly.Specs.Retry +{ + [Collection("SystemClockDependantCollection")] + public class WaitAndRetryForeverTResultSpecs : IDisposable + { + public WaitAndRetryForeverTResultSpecs() + { + // do nothing on call to sleep + SystemClock.Sleep = (_, __) => { }; + } + + [Fact] + public void Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() + { + Dictionary expectedRetryWaits = new Dictionary(){ + + {ResultPrimitive.Fault, 2.Seconds()}, + {ResultPrimitive.FaultAgain, 4.Seconds()}, + }; + + var actualRetryWaits = new List(); + + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .WaitAndRetryForever( + (retryAttempt, outcome, ctx) => expectedRetryWaits[outcome.Result], + (_, timeSpan, __) => actualRetryWaits.Add(timeSpan) + ); + + using (var enumerator = expectedRetryWaits.GetEnumerator()) + { + policy.Execute(() => + { + if (enumerator.MoveNext()) return enumerator.Current.Key; + else return ResultPrimitive.Undefined; + }); + } + + actualRetryWaits.Should().ContainInOrder(expectedRetryWaits.Values); + } + + public void Dispose() + { + SystemClock.Reset(); + } + + } +} \ No newline at end of file diff --git a/src/Polly.SharedSpecs/Retry/WaitAndRetrySpecs.cs b/src/Polly.SharedSpecs/Retry/WaitAndRetrySpecs.cs index 151ecec5359..98efbe48ffe 100644 --- a/src/Polly.SharedSpecs/Retry/WaitAndRetrySpecs.cs +++ b/src/Polly.SharedSpecs/Retry/WaitAndRetrySpecs.cs @@ -706,6 +706,57 @@ public void Should_calculate_retry_timespans_from_current_retry_attempt_and_time .ContainInOrder(expectedRetryWaits); } + [Fact] + public void Should_be_able_to_pass_handled_exception_to_sleepdurationprovider() + { + object capturedExceptionInstance = null; + + DivideByZeroException exceptionInstance = new DivideByZeroException(); + + var policy = Policy + .Handle() + .WaitAndRetry(5, + sleepDurationProvider: (retries, ex, ctx) => + { + capturedExceptionInstance = ex; + return TimeSpan.FromMilliseconds(0); + }, + onRetry: (ex, ts, i, ctx) => + { + } + ); + + policy.RaiseException(exceptionInstance); + + capturedExceptionInstance.Should().Be(exceptionInstance); + } + + [Fact] + public void Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() + { + Dictionary expectedRetryWaits = new Dictionary(){ + + {new DivideByZeroException(), 2.Seconds()}, + {new ArgumentNullException(), 4.Seconds()}, + }; + + var actualRetryWaits = new List(); + + var policy = Policy + .Handle() + .WaitAndRetry(2, + (retryAttempt, exc, ctx) => expectedRetryWaits[exc], + (_, timeSpan, __, ___) => actualRetryWaits.Add(timeSpan) + ); + + using (var enumerator = expectedRetryWaits.GetEnumerator()) + { + policy.Execute(() => { if (enumerator.MoveNext()) throw enumerator.Current.Key; }); + } + + actualRetryWaits.Should().ContainInOrder(expectedRetryWaits.Values); + } + [Fact] public void Should_be_able_to_pass_retry_duration_from_execution_to_sleepDurationProvider_via_context() { @@ -1138,12 +1189,12 @@ public void Should_honour_and_report_cancellation_during_func_execution() attemptsInvoked.Should().Be(1); } + #endregion + public void Dispose() { SystemClock.Reset(); } - #endregion - } } \ No newline at end of file diff --git a/src/Polly.SharedSpecs/Retry/WaitAndRetryTResultAsyncSpecs.cs b/src/Polly.SharedSpecs/Retry/WaitAndRetryTResultAsyncSpecs.cs new file mode 100644 index 00000000000..aff1326ecee --- /dev/null +++ b/src/Polly.SharedSpecs/Retry/WaitAndRetryTResultAsyncSpecs.cs @@ -0,0 +1,61 @@ +using System; +using System.Threading.Tasks; +using System.Collections.Generic; +using FluentAssertions; +using Polly.Specs.Helpers; +using Polly.Utilities; +using Xunit; + +namespace Polly.Specs.Retry +{ + [Collection("SystemClockDependantCollection")] + public class WaitAndRetryTResultAsyncSpecs : IDisposable + { + public WaitAndRetryTResultAsyncSpecs() + { + // do nothing on call to sleep + SystemClock.SleepAsync = (_, __) => TaskHelper.EmptyTask; + } + + [Fact] + public async Task Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() + { + Dictionary expectedRetryWaits = new Dictionary(){ + + {ResultPrimitive.Fault, 2.Seconds()}, + {ResultPrimitive.FaultAgain, 4.Seconds()}, + }; + + var actualRetryWaits = new List(); + + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .WaitAndRetryAsync(2, + (retryAttempt, outcome, ctx) => expectedRetryWaits[outcome.Result], + (_, timeSpan, __, ___) => + { + actualRetryWaits.Add(timeSpan); + return TaskHelper.EmptyTask; + }); + + using (var enumerator = expectedRetryWaits.GetEnumerator()) + { + await policy.ExecuteAsync(async () => + { + await TaskHelper.EmptyTask; + if (enumerator.MoveNext()) return enumerator.Current.Key; + else return ResultPrimitive.Undefined; + }); + } + + actualRetryWaits.Should().ContainInOrder(expectedRetryWaits.Values); + } + + public void Dispose() + { + SystemClock.Reset(); + } + + } +} \ No newline at end of file diff --git a/src/Polly.SharedSpecs/Retry/WaitAndRetryTResultSpecs.cs b/src/Polly.SharedSpecs/Retry/WaitAndRetryTResultSpecs.cs new file mode 100644 index 00000000000..4c74ad76c93 --- /dev/null +++ b/src/Polly.SharedSpecs/Retry/WaitAndRetryTResultSpecs.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using FluentAssertions; +using Polly.Specs.Helpers; +using Polly.Utilities; +using Xunit; + +namespace Polly.Specs.Retry +{ + [Collection("SystemClockDependantCollection")] + public class WaitAndRetryTResultSpecs : IDisposable + { + public WaitAndRetryTResultSpecs() + { + // do nothing on call to sleep + SystemClock.Sleep = (_, __) => { }; + } + + [Fact] + public void Should_be_able_to_calculate_retry_timespans_based_on_the_handled_fault() + { + Dictionary expectedRetryWaits = new Dictionary(){ + + {ResultPrimitive.Fault, 2.Seconds()}, + {ResultPrimitive.FaultAgain, 4.Seconds()}, + }; + + var actualRetryWaits = new List(); + + var policy = Policy + .HandleResult(ResultPrimitive.Fault) + .OrResult(ResultPrimitive.FaultAgain) + .WaitAndRetry(2, + (retryAttempt, outcome, ctx) => expectedRetryWaits[outcome.Result], + (_, timeSpan, __, ___) => actualRetryWaits.Add(timeSpan) + ); + + using (var enumerator = expectedRetryWaits.GetEnumerator()) + { + policy.Execute(() => + { + if (enumerator.MoveNext()) return enumerator.Current.Key; + else return ResultPrimitive.Undefined; + }); + } + + actualRetryWaits.Should().ContainInOrder(expectedRetryWaits.Values); + } + + public void Dispose() + { + SystemClock.Reset(); + } + + } +} \ No newline at end of file diff --git a/src/Polly.nuspec b/src/Polly.nuspec index 9859fb67faf..1bb31221b33 100644 --- a/src/Polly.nuspec +++ b/src/Polly.nuspec @@ -18,7 +18,8 @@ 5.6.0 --------------------- - Bug fix: set context keys for generic execute method with PolicyWrap - - Add GetPolicies extension method to IPolicyWrap + - Add GetPolicies extension method to IPolicyWrap + - Allow WaitAndRetry policies to calculate wait based on the handled fault 5.5.0 ---------------------