diff --git a/src/Polly.Core.Benchmarks/Utils/Helper.Retry.cs b/src/Polly.Core.Benchmarks/Utils/Helper.Retry.cs index 06cdd218c25..787c19e1029 100644 --- a/src/Polly.Core.Benchmarks/Utils/Helper.Retry.cs +++ b/src/Polly.Core.Benchmarks/Utils/Helper.Retry.cs @@ -1,4 +1,5 @@ using System; +using Polly.Strategy; namespace Polly.Core.Benchmarks.Utils; @@ -18,17 +19,19 @@ public static object CreateRetry(PollyVersion technology) PollyVersion.V8 => CreateStrategy(builder => { - var options = new RetryStrategyOptions + builder.AddRetry(new RetryStrategyOptions { RetryCount = 3, BackoffType = RetryBackoffType.Constant, - BaseDelay = delay - }; - - options.ShouldRetry.HandleOutcome((outcome, _) => outcome.Result == Failure || outcome.Exception is InvalidOperationException); - options.OnRetry.Register(() => { }); - - builder.AddRetry(options); + BaseDelay = delay, + ShouldRetry = (outcome, _) => outcome switch + { + { Exception: InvalidOperationException } => PredicateResult.True, + { Result: string result } when result == Failure => PredicateResult.True, + _ => PredicateResult.False + }, + OnRetry = (_, _) => default + }); }), _ => throw new NotSupportedException() }; diff --git a/src/Polly.Core.Tests/Issues/IssuesTests.FlowingContext_849.cs b/src/Polly.Core.Tests/Issues/IssuesTests.FlowingContext_849.cs index b1826493523..1fcbb2bfb08 100644 --- a/src/Polly.Core.Tests/Issues/IssuesTests.FlowingContext_849.cs +++ b/src/Polly.Core.Tests/Issues/IssuesTests.FlowingContext_849.cs @@ -1,4 +1,5 @@ using Polly.Retry; +using Polly.Strategy; namespace Polly.Core.Tests.Issues; @@ -8,19 +9,20 @@ public partial class IssuesTests public void FlowingContext_849() { var contextChecked = false; - var retryOptions = new RetryStrategyOptions(); - - // configure the predicate and use the context - retryOptions.ShouldRetry.HandleResult((_, args) => - { - // access the context to evaluate the retry - ResilienceContext context = args.Context; - context.Should().NotBeNull(); - contextChecked = true; - return false; - }); - - var strategy = new ResilienceStrategyBuilder().AddRetry(retryOptions).Build(); + var strategy = new ResilienceStrategyBuilder() + .AddRetry(new RetryStrategyOptions + { + // configure the predicate and use the context + ShouldRetry = (_, args) => + { + // access the context to evaluate the retry + ResilienceContext context = args.Context; + context.Should().NotBeNull(); + contextChecked = true; + return PredicateResult.False; + } + }) + .Build(); // execute the retry strategy.Execute(_ => 0); diff --git a/src/Polly.Core.Tests/Issues/IssuesTests.HandleMultipleResults_898.cs b/src/Polly.Core.Tests/Issues/IssuesTests.HandleMultipleResults_898.cs index 12f7f0559d9..0bbebb8e93c 100644 --- a/src/Polly.Core.Tests/Issues/IssuesTests.HandleMultipleResults_898.cs +++ b/src/Polly.Core.Tests/Issues/IssuesTests.HandleMultipleResults_898.cs @@ -1,4 +1,5 @@ using Polly.Retry; +using Polly.Strategy; namespace Polly.Core.Tests.Issues; @@ -13,17 +14,23 @@ public void HandleMultipleResults_898() BackoffType = RetryBackoffType.Constant, RetryCount = 1, BaseDelay = TimeSpan.FromMilliseconds(1), + ShouldRetry = (outcome, _) => outcome switch + { + // handle string results + { Result: string res } when res == "error" => PredicateResult.True, + + // handle int results + { Result: int res } when res == -1 => PredicateResult.True, + _ => PredicateResult.False + }, + OnRetry = (_, args) => + { + // add a callback updates the resilience context with the retry marker + args.Context.Properties.Set(isRetryKey, true); + return default; + } }; - // now add a callback updates the resilience context with the retry marker - options.OnRetry.Register((_, args) => args.Context.Properties.Set(isRetryKey, true)); - - // handle int results - options.ShouldRetry.HandleResult(-1); - - // handle string results - options.ShouldRetry.HandleResult("error"); - // create the strategy var strategy = new ResilienceStrategyBuilder { TimeProvider = TimeProvider.Object }.AddRetry(options).Build(); diff --git a/src/Polly.Core.Tests/Retry/RetryResilienceStrategyBuilderExtensionsTests.cs b/src/Polly.Core.Tests/Retry/RetryResilienceStrategyBuilderExtensionsTests.cs index 04b52e1a8d5..14c04dce4ec 100644 --- a/src/Polly.Core.Tests/Retry/RetryResilienceStrategyBuilderExtensionsTests.cs +++ b/src/Polly.Core.Tests/Retry/RetryResilienceStrategyBuilderExtensionsTests.cs @@ -16,7 +16,8 @@ public class RetryResilienceStrategyBuilderExtensionsTests { BackoffType = RetryBackoffType.Exponential, RetryCount = 3, - BaseDelay = TimeSpan.FromSeconds(2) + BaseDelay = TimeSpan.FromSeconds(2), + ShouldRetry = (_, _) => PredicateResult.True, }); AssertStrategy(builder, RetryBackoffType.Exponential, 3, TimeSpan.FromSeconds(2)); @@ -32,12 +33,12 @@ public class RetryResilienceStrategyBuilderExtensionsTests }, builder => { - builder.AddRetry(retry=>retry.HandleResult(10), RetryBackoffType.Linear); + builder.AddRetry(retry => retry.HandleResult(10), RetryBackoffType.Linear); AssertStrategy(builder, RetryBackoffType.Linear, 3, TimeSpan.FromSeconds(2)); }, builder => { - builder.AddRetry(retry=>retry.HandleResult(10), RetryBackoffType.Linear, 2, TimeSpan.FromSeconds(1)); + builder.AddRetry(retry => retry.HandleResult(10), RetryBackoffType.Linear, 2, TimeSpan.FromSeconds(1)); AssertStrategy(builder, RetryBackoffType.Linear, 2, TimeSpan.FromSeconds(1)); }, builder => @@ -46,9 +47,9 @@ public class RetryResilienceStrategyBuilderExtensionsTests AssertStrategy(builder, RetryBackoffType.ExponentialWithJitter, 3, TimeSpan.FromSeconds(2), strategy => { - var args = new RetryDelayArguments(ResilienceContext.Get(), 8, TimeSpan.Zero); + var args = new RetryDelayArguments(ResilienceContext.Get().Initialize(true), 8, TimeSpan.Zero); - strategy.DelayGenerator!.GenerateAsync(new Outcome(new InvalidOperationException()), args).Result.Should().Be(TimeSpan.FromMilliseconds(8)); + strategy.DelayGenerator!(new Outcome(new InvalidOperationException()), args).Result.Should().Be(TimeSpan.FromMilliseconds(8)); }); }, builder => @@ -57,7 +58,8 @@ public class RetryResilienceStrategyBuilderExtensionsTests { BackoffType = RetryBackoffType.Exponential, RetryCount = 3, - BaseDelay = TimeSpan.FromSeconds(2) + BaseDelay = TimeSpan.FromSeconds(2), + ShouldRetry = (_, _) => PredicateResult.True }); AssertStrategy(builder, RetryBackoffType.Exponential, 3, TimeSpan.FromSeconds(2)); @@ -86,7 +88,7 @@ public void AddRetry_GenericOverloads_Ok(Action> public void AddRetry_DefaultOptions_Ok() { var builder = new ResilienceStrategyBuilder(); - var options = new RetryStrategyOptions(); + var options = new RetryStrategyOptions { ShouldRetry = (_, _) => PredicateResult.True }; builder.AddRetry(options); diff --git a/src/Polly.Core.Tests/Retry/RetryResilienceStrategyTests.cs b/src/Polly.Core.Tests/Retry/RetryResilienceStrategyTests.cs index eac39ca2623..f3fa6c01053 100644 --- a/src/Polly.Core.Tests/Retry/RetryResilienceStrategyTests.cs +++ b/src/Polly.Core.Tests/Retry/RetryResilienceStrategyTests.cs @@ -18,7 +18,7 @@ public class RetryResilienceStrategyTests public void ShouldRetryEmpty_Skipped() { bool called = false; - _options.OnRetry.Register(() => called = true); + _options.OnRetry = (_, _) => { called = true; return default; }; SetupNoDelay(); var sut = CreateSut(); @@ -44,7 +44,7 @@ public void ExecuteAsync_MultipleRetries_EnsureDiscardedResultsDisposed() _options.RetryCount = 5; SetupNoDelay(); _timeProvider.SetupAnyDelay(); - _options.ShouldRetry.HandleResult(_ => true); + _options.ShouldRetry = (_, _) => PredicateResult.True; var results = new List(); var sut = CreateSut(); @@ -69,8 +69,8 @@ public void ExecuteAsync_MultipleRetries_EnsureDiscardedResultsDisposed() public void Retry_RetryCount_Respected() { int calls = 0; - _options.OnRetry.Register(() => calls++); - _options.ShouldRetry.HandleResult(0); + _options.OnRetry = (_, _) => { calls++; return default; }; + _options.ShouldRetry = (outcome, _) => outcome.ResultPredicateAsync(0); _options.RetryCount = 12; SetupNoDelay(); var sut = CreateSut(); @@ -84,12 +84,14 @@ public void Retry_RetryCount_Respected() public void RetryException_RetryCount_Respected() { int calls = 0; - _options.OnRetry.Register((args, _) => + _options.OnRetry = (outcome, _) => { - args.Exception.Should().BeOfType(); + outcome.Exception.Should().BeOfType(); calls++; - }); - _options.ShouldRetry.HandleException(); + return default; + }; + + _options.ShouldRetry = (outcome, _) => outcome.ExceptionPredicateAsync(); _options.RetryCount = 3; SetupNoDelay(); var sut = CreateSut(); @@ -104,7 +106,7 @@ public void Retry_Infinite_Respected() { int calls = 0; _options.BackoffType = RetryBackoffType.Constant; - _options.OnRetry.Register((_, args) => + _options.OnRetry = (_, args) => { if (args.Attempt > RetryConstants.MaxRetryCount) { @@ -112,8 +114,9 @@ public void Retry_Infinite_Respected() } calls++; - }); - _options.ShouldRetry.HandleResult(0); + return default; + }; + _options.ShouldRetry = (outcome, _) => outcome.ResultPredicateAsync(0); _options.RetryCount = RetryStrategyOptions.InfiniteRetryCount; SetupNoDelay(); var sut = CreateSut(); @@ -127,11 +130,11 @@ public void Retry_Infinite_Respected() public void RetryDelayGenerator_Respected() { int calls = 0; - _options.OnRetry.Register(() => calls++); - _options.ShouldRetry.HandleResult(0); + _options.OnRetry = (_, _) => { calls++; return default; }; + _options.ShouldRetry = (outcome, _) => outcome.ResultPredicateAsync(0); _options.RetryCount = 3; _options.BackoffType = RetryBackoffType.Constant; - _options.RetryDelayGenerator.SetGenerator((_, _) => TimeSpan.FromMilliseconds(123)); + _options.RetryDelayGenerator = (_, _) => new ValueTask(TimeSpan.FromMilliseconds(123)); _timeProvider.SetupDelay(TimeSpan.FromMilliseconds(123)); var sut = CreateSut(); @@ -146,16 +149,17 @@ public void OnRetry_EnsureCorrectArguments() { var attempts = new List(); var delays = new List(); - _options.OnRetry.Register((outcome, args) => + _options.OnRetry = (outcome, args) => { attempts.Add(args.Attempt); delays.Add(args.RetryDelay); outcome.Exception.Should().BeNull(); outcome.Result.Should().Be(0); - }); + return default; + }; - _options.ShouldRetry.HandleResult(0); + _options.ShouldRetry = (outcome, _) => outcome.ResultPredicateAsync(0); _options.RetryCount = 3; _options.BackoffType = RetryBackoffType.Linear; _timeProvider.SetupAnyDelay(); @@ -182,7 +186,7 @@ public void OnRetry_EnsureTelemetry() _diagnosticSource.Setup(v => v.IsEnabled("OnRetry")).Returns(true); - _options.ShouldRetry.HandleResult(0); + _options.ShouldRetry = (outcome, _) => outcome.ResultPredicateAsync(0); _options.RetryCount = 3; _options.BackoffType = RetryBackoffType.Linear; _timeProvider.SetupAnyDelay(); @@ -199,7 +203,7 @@ public void RetryDelayGenerator_EnsureCorrectArguments() { var attempts = new List(); var hints = new List(); - _options.RetryDelayGenerator.SetGenerator((outcome, args) => + _options.RetryDelayGenerator = (outcome, args) => { attempts.Add(args.Attempt); hints.Add(args.DelayHint); @@ -207,10 +211,10 @@ public void RetryDelayGenerator_EnsureCorrectArguments() outcome.Exception.Should().BeNull(); outcome.Result.Should().Be(0); - return TimeSpan.Zero; - }); + return new ValueTask(TimeSpan.Zero); + }; - _options.ShouldRetry.HandleResult(0); + _options.ShouldRetry = (outcome, _) => outcome.ResultPredicateAsync(0); _options.RetryCount = 3; _options.BackoffType = RetryBackoffType.Linear; _timeProvider.SetupAnyDelay(); @@ -229,10 +233,7 @@ public void RetryDelayGenerator_EnsureCorrectArguments() hints[2].Should().Be(TimeSpan.FromSeconds(6)); } - private void SetupNoDelay() => _options.RetryDelayGenerator.SetGenerator((_, _) => TimeSpan.Zero); + private void SetupNoDelay() => _options.RetryDelayGenerator = (_, _) => new ValueTask(TimeSpan.Zero); - private RetryResilienceStrategy CreateSut() - { - return new RetryResilienceStrategy(_options, _timeProvider.Object, _telemetry, new RandomUtil(0)); - } + private RetryResilienceStrategy CreateSut() => new(_options, _timeProvider.Object, _telemetry, new RandomUtil(0)); } diff --git a/src/Polly.Core.Tests/Retry/RetryStrategyOptionsTResultTests.cs b/src/Polly.Core.Tests/Retry/RetryStrategyOptionsTResultTests.cs index 57a8e68df2d..5e8d6bd4178 100644 --- a/src/Polly.Core.Tests/Retry/RetryStrategyOptionsTResultTests.cs +++ b/src/Polly.Core.Tests/Retry/RetryStrategyOptionsTResultTests.cs @@ -13,14 +13,11 @@ public void Ctor_Ok() var options = new RetryStrategyOptions(); options.StrategyType.Should().Be("Retry"); - options.ShouldRetry.Should().NotBeNull(); - options.ShouldRetry.IsEmpty.Should().BeTrue(); + options.ShouldRetry.Should().BeNull(); - options.RetryDelayGenerator.Should().NotBeNull(); - options.RetryDelayGenerator.IsEmpty.Should().BeTrue(); + options.RetryDelayGenerator.Should().BeNull(); - options.OnRetry.Should().NotBeNull(); - options.OnRetry.IsEmpty.Should().BeTrue(); + options.OnRetry.Should().BeNull(); options.RetryCount.Should().Be(3); options.BackoffType.Should().Be(RetryBackoffType.ExponentialWithJitter); @@ -49,8 +46,6 @@ Invalid Options The field RetryCount must be between -1 and 100. The field BaseDelay must be >= to 00:00:00. The ShouldRetry field is required. - The RetryDelayGenerator field is required. - The OnRetry field is required. """); } @@ -64,11 +59,11 @@ public async Task AsNonGenericOptions_Ok() BaseDelay = TimeSpan.FromMilliseconds(555), RetryCount = 7, StrategyName = "my-name", + ShouldRetry = (outcome, _) => new ValueTask(outcome.HasResult && outcome.Result == 999), + OnRetry = (_, _) => { called = true; return default; }, + RetryDelayGenerator = (_, _) => new ValueTask(TimeSpan.FromSeconds(123)) }; - options.ShouldRetry.HandleResult(999); - options.OnRetry.Register(() => called = true); - options.RetryDelayGenerator.SetGenerator((_, _) => TimeSpan.FromSeconds(123)); var nonGenericOptions = options.AsNonGenericOptions(); nonGenericOptions.BackoffType.Should().Be(RetryBackoffType.Constant); @@ -77,13 +72,21 @@ public async Task AsNonGenericOptions_Ok() nonGenericOptions.StrategyName.Should().Be("my-name"); nonGenericOptions.StrategyType.Should().Be("Retry"); - var args = new ShouldRetryArguments(ResilienceContext.Get(), 0); - var delayArgs = new RetryDelayArguments(ResilienceContext.Get(), 2, TimeSpan.FromMinutes(1)); - var retryArgs = new OnRetryArguments(ResilienceContext.Get(), 0, TimeSpan.Zero); + // wrong result type + var context = ResilienceContext.Get().Initialize(true); + var args = new ShouldRetryArguments(context, 0); + var delayArgs = new RetryDelayArguments(context, 2, TimeSpan.FromMinutes(1)); + var retryArgs = new OnRetryArguments(context, 0, TimeSpan.Zero); + (await nonGenericOptions.ShouldRetry!(new Outcome(999), args)).Should().BeFalse(); + await nonGenericOptions.OnRetry!(new Outcome(999), retryArgs); + (await nonGenericOptions.RetryDelayGenerator!(new Outcome(999), delayArgs)).Should().Be(TimeSpan.MinValue); + called.Should().BeFalse(); - (await nonGenericOptions.ShouldRetry.CreateHandler()!.ShouldHandleAsync(new Outcome(999), args)).Should().BeTrue(); - await nonGenericOptions.OnRetry.CreateHandler()!.HandleAsync(new Outcome(999), retryArgs); + // correct result type + context.Initialize(true); + (await nonGenericOptions.ShouldRetry!(new Outcome(999), args)).Should().BeTrue(); + await nonGenericOptions.OnRetry!(new Outcome(999), retryArgs); + (await nonGenericOptions.RetryDelayGenerator!(new Outcome(999), delayArgs)).Should().Be(TimeSpan.FromSeconds(123)); called.Should().BeTrue(); - (await nonGenericOptions.RetryDelayGenerator.CreateHandler(default, _ => true)!.GenerateAsync(new Outcome(999), delayArgs)).Should().Be(TimeSpan.FromSeconds(123)); } } diff --git a/src/Polly.Core.Tests/Retry/RetryStrategyOptionsTests.cs b/src/Polly.Core.Tests/Retry/RetryStrategyOptionsTests.cs index 0916cbb88ed..1870121c855 100644 --- a/src/Polly.Core.Tests/Retry/RetryStrategyOptionsTests.cs +++ b/src/Polly.Core.Tests/Retry/RetryStrategyOptionsTests.cs @@ -11,14 +11,9 @@ public void Ctor_Ok() { var options = new RetryStrategyOptions(); - options.ShouldRetry.Should().NotBeNull(); - options.ShouldRetry.IsEmpty.Should().BeTrue(); - - options.RetryDelayGenerator.Should().NotBeNull(); - options.RetryDelayGenerator.IsEmpty.Should().BeTrue(); - - options.OnRetry.Should().NotBeNull(); - options.OnRetry.IsEmpty.Should().BeTrue(); + options.ShouldRetry.Should().BeNull(); + options.RetryDelayGenerator.Should().BeNull(); + options.OnRetry.Should().BeNull(); options.RetryCount.Should().Be(3); options.BackoffType.Should().Be(RetryBackoffType.ExponentialWithJitter); @@ -47,8 +42,6 @@ Invalid Options The field RetryCount must be between -1 and 100. The field BaseDelay must be >= to 00:00:00. The ShouldRetry field is required. - The RetryDelayGenerator field is required. - The OnRetry field is required. """); } } diff --git a/src/Polly.Core/Retry/RetryResilienceStrategy.cs b/src/Polly.Core/Retry/RetryResilienceStrategy.cs index 4a82067f8d2..8b684f3c676 100644 --- a/src/Polly.Core/Retry/RetryResilienceStrategy.cs +++ b/src/Polly.Core/Retry/RetryResilienceStrategy.cs @@ -14,9 +14,9 @@ public RetryResilienceStrategy(RetryStrategyOptions options, TimeProvider timePr _timeProvider = timeProvider; _telemetry = telemetry; _randomUtil = randomUtil; - OnRetry = options.OnRetry.CreateHandler(); - DelayGenerator = options.RetryDelayGenerator.CreateHandler(TimeSpan.MinValue, RetryHelper.IsValidDelay); - ShouldRetry = options.ShouldRetry.CreateHandler(); + OnRetry = options.OnRetry; + DelayGenerator = options.RetryDelayGenerator; + ShouldRetry = options.ShouldRetry!; BackoffType = options.BackoffType; BaseDelay = options.BaseDelay; @@ -29,11 +29,11 @@ public RetryResilienceStrategy(RetryStrategyOptions options, TimeProvider timePr public int RetryCount { get; } - public OutcomePredicate.Handler? ShouldRetry { get; } + public Func> ShouldRetry { get; set; } - public OutcomeGenerator.Handler? DelayGenerator { get; } + public Func>? DelayGenerator { get; set; } - public OutcomeEvent.Handler? OnRetry { get; } + public Func? OnRetry { get; set; } protected internal override async ValueTask> ExecuteCoreAsync( Func>> callback, @@ -54,15 +54,15 @@ protected internal override async ValueTask> ExecuteCoreAsync outcome = await callback(context, state).ConfigureAwait(context.ContinueOnCapturedContext); - if (IsLastAttempt(attempt) || !await ShouldRetry.ShouldHandleAsync(outcome, new ShouldRetryArguments(context, attempt)).ConfigureAwait(context.ContinueOnCapturedContext)) + if (IsLastAttempt(attempt) || !await ShouldRetry(outcome.AsOutcome(), new ShouldRetryArguments(context, attempt)).ConfigureAwait(context.ContinueOnCapturedContext)) { return outcome; } var delay = RetryHelper.GetRetryDelay(BackoffType, attempt, BaseDelay, ref retryState, _randomUtil); - if (DelayGenerator != null) + if (DelayGenerator is not null) { - var newDelay = await DelayGenerator.GenerateAsync(outcome, new RetryDelayArguments(context, attempt, delay)).ConfigureAwait(false); + var newDelay = await DelayGenerator(outcome.AsOutcome(), new RetryDelayArguments(context, attempt, delay)).ConfigureAwait(false); if (RetryHelper.IsValidDelay(newDelay)) { delay = newDelay; @@ -73,9 +73,9 @@ protected internal override async ValueTask> ExecuteCoreAsync AddRetry( var options = new RetryStrategyOptions(); ConfigureShouldRetry(shouldRetry, options); - options.RetryDelayGenerator.SetGenerator((_, args) => retryDelayGenerator(args.Attempt)); + options.RetryDelayGenerator = (_, args) => new ValueTask(retryDelayGenerator(args.Attempt)); return builder.AddRetry(options); } @@ -163,6 +163,6 @@ private static void ConfigureShouldRetry(Action(); shouldRetry(predicate); - options.ShouldRetry.HandleOutcome(predicate.CreatePredicate()); + options.ShouldRetry = predicate.CreatePredicate(); } } diff --git a/src/Polly.Core/Retry/RetryStrategyOptions.TResult.cs b/src/Polly.Core/Retry/RetryStrategyOptions.TResult.cs index bba90d21ed0..225295bf7f3 100644 --- a/src/Polly.Core/Retry/RetryStrategyOptions.TResult.cs +++ b/src/Polly.Core/Retry/RetryStrategyOptions.TResult.cs @@ -62,19 +62,18 @@ public class RetryStrategyOptions : ResilienceStrategyOptions /// Gets or sets an outcome predicate that is used to register the predicates to determine if a retry should be performed. /// /// - /// By default, the predicate is empty and no results or exceptions are retried. + /// Defaults to . This property is required. /// [Required] - public OutcomePredicate ShouldRetry { get; set; } = new(); + public Func, ShouldRetryArguments, ValueTask>? ShouldRetry { get; set; } /// /// Gets or sets the generator instance that is used to calculate the time between retries. /// /// - /// By default, the generator is empty and it does not affect the delay between retries. + /// Defaults to . /// - [Required] - public OutcomeGenerator RetryDelayGenerator { get; set; } = new(); + public Func, RetryDelayArguments, ValueTask>? RetryDelayGenerator { get; set; } /// /// Gets or sets an outcome event that is used to register on-retry callbacks. @@ -86,21 +85,65 @@ public class RetryStrategyOptions : ResilienceStrategyOptions /// you need to preserve the result for further processing, create the copy of the result or extract and store all necessary information /// from the result within the event. /// + /// + /// Defaults to . + /// /// - [Required] - public OutcomeEvent OnRetry { get; set; } = new(); + public Func, OnRetryArguments, ValueTask>? OnRetry { get; set; } internal RetryStrategyOptions AsNonGenericOptions() { - return new RetryStrategyOptions + var options = new RetryStrategyOptions { BackoffType = BackoffType, BaseDelay = BaseDelay, RetryCount = RetryCount, - OnRetry = new OutcomeEvent().SetCallbacks(OnRetry), - RetryDelayGenerator = new OutcomeGenerator().SetGenerator(RetryDelayGenerator), - ShouldRetry = new OutcomePredicate().SetPredicates(ShouldRetry), StrategyName = StrategyName, }; + + if (ShouldRetry is var shouldRetry) + { + options.ShouldRetry = (outcome, args) => + { + if (args.Context.ResultType != typeof(TResult)) + { + return PredicateResult.False; + } + + return shouldRetry!(outcome.AsOutcome(), args); + }; + } + + if (OnRetry is var onRetry) + { + // no need to do type-check again because result will be retried so the type check was + // already done in ShouldRetry + options.OnRetry = (outcome, args) => + { + if (args.Context.ResultType != typeof(TResult)) + { + return default; + } + + return onRetry!(outcome.AsOutcome(), args); + }; + } + + if (RetryDelayGenerator is var generator) + { + // no need to do type-check again because result will be retried so the type check was + // already done in ShouldRetry + options.RetryDelayGenerator = (outcome, args) => + { + if (args.Context.ResultType != typeof(TResult)) + { + return new ValueTask(TimeSpan.MinValue); + } + + return generator!(outcome.AsOutcome(), args); + }; + } + + return options; } } diff --git a/src/Polly.Core/Retry/RetryStrategyOptions.cs b/src/Polly.Core/Retry/RetryStrategyOptions.cs index 1a69370412b..e8bc6abd89a 100644 --- a/src/Polly.Core/Retry/RetryStrategyOptions.cs +++ b/src/Polly.Core/Retry/RetryStrategyOptions.cs @@ -66,19 +66,18 @@ public class RetryStrategyOptions : ResilienceStrategyOptions /// Gets or sets an outcome predicate that is used to register the predicates to determine if a retry should be performed. /// /// - /// By default, the predicate is empty and no results or exceptions are retried. + /// Defaults to . This property is required. /// [Required] - public OutcomePredicate ShouldRetry { get; set; } = new(); + public Func>? ShouldRetry { get; set; } /// /// Gets or sets the generator instance that is used to calculate the time between retries. /// /// - /// By default, the generator is empty and it does not affect the delay between retries. + /// Defaults to . /// - [Required] - public OutcomeGenerator RetryDelayGenerator { get; set; } = new(); + public Func>? RetryDelayGenerator { get; set; } /// /// Gets or sets an outcome event that is used to register on-retry callbacks. @@ -90,7 +89,9 @@ public class RetryStrategyOptions : ResilienceStrategyOptions /// you need to preserve the result for further processing, create the copy of the result or extract and store all necessary information /// from the result within the event. /// + /// + /// Defaults to . + /// /// - [Required] - public OutcomeEvent OnRetry { get; set; } = new(); + public Func? OnRetry { get; set; } } diff --git a/src/Polly.Core/Strategy/OutcomeGenerator.TResult.cs b/src/Polly.Core/Strategy/OutcomeGenerator.TResult.cs index 198a369377d..4e03aa87bcf 100644 --- a/src/Polly.Core/Strategy/OutcomeGenerator.TResult.cs +++ b/src/Polly.Core/Strategy/OutcomeGenerator.TResult.cs @@ -17,11 +17,6 @@ public sealed class OutcomeGenerator { private Func, TArgs, ValueTask>? _generator; - /// - /// Gets a value indicating whether the generator is empty. - /// - internal bool IsEmpty => _generator is null; - /// /// Sets a result generator for a specific result type. /// diff --git a/src/Polly.TestUtils/OutcomeExtensions.cs b/src/Polly.TestUtils/OutcomeExtensions.cs new file mode 100644 index 00000000000..6243586d061 --- /dev/null +++ b/src/Polly.TestUtils/OutcomeExtensions.cs @@ -0,0 +1,37 @@ +using Polly.Strategy; + +namespace Polly.TestUtils; + +public static class OutcomeExtensions +{ + public static ValueTask ResultPredicateAsync(this Outcome outcome, TResult result) => new(outcome.ResultPredicate(result)); + + public static ValueTask ResultPredicateAsync(this Outcome outcome, Predicate predicate) => new(outcome.ResultPredicate(predicate)); + + public static bool ResultPredicate(this Outcome outcome, TResult result) + { + return outcome.ResultPredicate(r => EqualityComparer.Default.Equals(r, result)); + } + + public static bool ResultPredicate(this Outcome outcome, Predicate predicate) + { + if (outcome.TryGetResult(out var result) && result is TResult typedResult) + { + return predicate(typedResult); + } + + return false; + } + + public static ValueTask ExceptionPredicateAsync(this Outcome outcome) + where TException : Exception => new(outcome.ExceptionPredicate()); + + public static ValueTask ExceptionPredicateAsync(this Outcome outcome, Predicate predicate) + where TException : Exception => new(outcome.ExceptionPredicate(predicate)); + + public static bool ExceptionPredicate(this Outcome outcome) + where TException : Exception => outcome.ExceptionPredicate(_ => true); + + public static bool ExceptionPredicate(this Outcome outcome, Predicate predicate) + where TException : Exception => outcome.Exception is TException typedException && predicate(typedException); +}