From ddb9f705140710d56958de985d51f5496da49580 Mon Sep 17 00:00:00 2001 From: Peter Csala Date: Wed, 30 Apr 2025 17:05:14 +0200 Subject: [PATCH 1/3] Add attemptnumber to HedgingPredicateArguments --- src/Polly.Core/Hedging/Controller/TaskExecution.cs | 2 +- src/Polly.Core/Hedging/HedgingPredicateArguments.cs | 9 ++++++++- src/Polly.Core/PublicAPI.Unshipped.txt | 2 ++ .../Hedging/HedgingPredicateArgumentsTests.cs | 3 ++- .../Hedging/HedgingStrategyOptionsTests.cs | 6 +++--- test/Polly.Core.Tests/PredicateBuilderTests.cs | 2 +- 6 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/Polly.Core/Hedging/Controller/TaskExecution.cs b/src/Polly.Core/Hedging/Controller/TaskExecution.cs index a87798351db..354b7db27bd 100644 --- a/src/Polly.Core/Hedging/Controller/TaskExecution.cs +++ b/src/Polly.Core/Hedging/Controller/TaskExecution.cs @@ -239,7 +239,7 @@ private async Task ExecutePrimaryActionAsync(Func outcome) { - var args = new HedgingPredicateArguments(Context, outcome); + var args = new HedgingPredicateArguments(Context, outcome, AttemptNumber); Outcome = outcome; IsHandled = await _handler.ShouldHandle(args).ConfigureAwait(Context.ContinueOnCapturedContext); TelemetryUtil.ReportExecutionAttempt(_telemetry, Context, outcome, AttemptNumber, ExecutionTime, IsHandled); diff --git a/src/Polly.Core/Hedging/HedgingPredicateArguments.cs b/src/Polly.Core/Hedging/HedgingPredicateArguments.cs index e8f8a5ef489..8123850a403 100644 --- a/src/Polly.Core/Hedging/HedgingPredicateArguments.cs +++ b/src/Polly.Core/Hedging/HedgingPredicateArguments.cs @@ -16,10 +16,12 @@ namespace Polly.Hedging; /// /// The context in which the resilience operation or event occurred. /// The outcome of the resilience operation or event. - public HedgingPredicateArguments(ResilienceContext context, Outcome outcome) + /// The zero-based attempt number. + public HedgingPredicateArguments(ResilienceContext context, Outcome outcome, int attemptNumber) { Context = context; Outcome = outcome; + AttemptNumber = attemptNumber; } /// @@ -31,4 +33,9 @@ public HedgingPredicateArguments(ResilienceContext context, Outcome out /// Gets the context of this event. /// public ResilienceContext Context { get; } + + /// + /// Gets the zero-based attempt number. + /// + public int AttemptNumber { get; } } diff --git a/src/Polly.Core/PublicAPI.Unshipped.txt b/src/Polly.Core/PublicAPI.Unshipped.txt index ab058de62d4..9da6458e06f 100644 --- a/src/Polly.Core/PublicAPI.Unshipped.txt +++ b/src/Polly.Core/PublicAPI.Unshipped.txt @@ -1 +1,3 @@ #nullable enable +Polly.Hedging.HedgingPredicateArguments.AttemptNumber.get -> int +Polly.Hedging.HedgingPredicateArguments.HedgingPredicateArguments(Polly.ResilienceContext! context, Polly.Outcome outcome, int attemptNumber) -> void diff --git a/test/Polly.Core.Tests/Hedging/HedgingPredicateArgumentsTests.cs b/test/Polly.Core.Tests/Hedging/HedgingPredicateArgumentsTests.cs index 5c4ef399025..5c77ac72141 100644 --- a/test/Polly.Core.Tests/Hedging/HedgingPredicateArgumentsTests.cs +++ b/test/Polly.Core.Tests/Hedging/HedgingPredicateArgumentsTests.cs @@ -11,10 +11,11 @@ public static void Ctor_Ok() var context = ResilienceContextPool.Shared.Get(); // Act - var args = new HedgingPredicateArguments(context, Outcome.FromResult(1)); + var args = new HedgingPredicateArguments(context, Outcome.FromResult(1), 0); // Assert args.Context.ShouldBe(context); args.Outcome.Result.ShouldBe(1); + args.AttemptNumber.ShouldBe(0); } } diff --git a/test/Polly.Core.Tests/Hedging/HedgingStrategyOptionsTests.cs b/test/Polly.Core.Tests/Hedging/HedgingStrategyOptionsTests.cs index ff8ed080d84..5ad6fe5ae04 100644 --- a/test/Polly.Core.Tests/Hedging/HedgingStrategyOptionsTests.cs +++ b/test/Polly.Core.Tests/Hedging/HedgingStrategyOptionsTests.cs @@ -57,9 +57,9 @@ public async Task ShouldHandle_EnsureDefaults() var options = new HedgingStrategyOptions(); var context = ResilienceContextPool.Shared.Get(); - (await options.ShouldHandle(new HedgingPredicateArguments(context, Outcome.FromResult(0)))).ShouldBe(false); - (await options.ShouldHandle(new HedgingPredicateArguments(context, Outcome.FromException(new OperationCanceledException())))).ShouldBe(false); - (await options.ShouldHandle(new HedgingPredicateArguments(context, Outcome.FromException(new InvalidOperationException())))).ShouldBe(true); + (await options.ShouldHandle(new HedgingPredicateArguments(context, Outcome.FromResult(0), 0))).ShouldBe(false); + (await options.ShouldHandle(new HedgingPredicateArguments(context, Outcome.FromException(new OperationCanceledException()), 1))).ShouldBe(false); + (await options.ShouldHandle(new HedgingPredicateArguments(context, Outcome.FromException(new InvalidOperationException()), 2))).ShouldBe(true); } [Fact] diff --git a/test/Polly.Core.Tests/PredicateBuilderTests.cs b/test/Polly.Core.Tests/PredicateBuilderTests.cs index d12f4098916..edb6baf14d4 100644 --- a/test/Polly.Core.Tests/PredicateBuilderTests.cs +++ b/test/Polly.Core.Tests/PredicateBuilderTests.cs @@ -117,7 +117,7 @@ public async Task Operator_HedgingStrategyOptions_Ok() ShouldHandle = new PredicateBuilder().HandleResult("error") }; - var handled = await options.ShouldHandle(new(context, CreateOutcome("error"))); + var handled = await options.ShouldHandle(new(context, CreateOutcome("error"), 0)); handled.ShouldBeTrue(); } From 05ce3abb623d6ea2a26fb997cd8621eb93541879 Mon Sep 17 00:00:00 2001 From: Peter Csala Date: Sat, 3 May 2025 00:06:46 +0200 Subject: [PATCH 2/3] Add AttemptNumber to HedgingPredicateArguments --- .../Hedging/HedgingPredicateArguments.cs | 15 +++++++++++---- src/Polly.Core/PublicAPI.Unshipped.txt | 2 +- .../Hedging/HedgingPredicateArgumentsTests.cs | 15 +++++++++++++++ 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/Polly.Core/Hedging/HedgingPredicateArguments.cs b/src/Polly.Core/Hedging/HedgingPredicateArguments.cs index 8123850a403..31bdfd8004e 100644 --- a/src/Polly.Core/Hedging/HedgingPredicateArguments.cs +++ b/src/Polly.Core/Hedging/HedgingPredicateArguments.cs @@ -16,14 +16,21 @@ namespace Polly.Hedging; /// /// The context in which the resilience operation or event occurred. /// The outcome of the resilience operation or event. - /// The zero-based attempt number. - public HedgingPredicateArguments(ResilienceContext context, Outcome outcome, int attemptNumber) + public HedgingPredicateArguments(ResilienceContext context, Outcome outcome) { Context = context; Outcome = outcome; - AttemptNumber = attemptNumber; } + /// + /// Initializes a new instance of the struct. + /// + /// The context in which the resilience operation or event occurred. + /// The outcome of the resilience operation or event. + /// The zero-based attempt number. + public HedgingPredicateArguments(ResilienceContext context, Outcome outcome, int attemptNumber) + : this(context, outcome) => AttemptNumber = attemptNumber; + /// /// Gets the outcome of the user-specified callback. /// @@ -37,5 +44,5 @@ public HedgingPredicateArguments(ResilienceContext context, Outcome out /// /// Gets the zero-based attempt number. /// - public int AttemptNumber { get; } + public int? AttemptNumber { get; } = null; } diff --git a/src/Polly.Core/PublicAPI.Unshipped.txt b/src/Polly.Core/PublicAPI.Unshipped.txt index 9da6458e06f..ab781236285 100644 --- a/src/Polly.Core/PublicAPI.Unshipped.txt +++ b/src/Polly.Core/PublicAPI.Unshipped.txt @@ -1,3 +1,3 @@ #nullable enable -Polly.Hedging.HedgingPredicateArguments.AttemptNumber.get -> int +Polly.Hedging.HedgingPredicateArguments.AttemptNumber.get -> int? Polly.Hedging.HedgingPredicateArguments.HedgingPredicateArguments(Polly.ResilienceContext! context, Polly.Outcome outcome, int attemptNumber) -> void diff --git a/test/Polly.Core.Tests/Hedging/HedgingPredicateArgumentsTests.cs b/test/Polly.Core.Tests/Hedging/HedgingPredicateArgumentsTests.cs index 5c77ac72141..ab6aefc4471 100644 --- a/test/Polly.Core.Tests/Hedging/HedgingPredicateArgumentsTests.cs +++ b/test/Polly.Core.Tests/Hedging/HedgingPredicateArgumentsTests.cs @@ -10,6 +10,21 @@ public static void Ctor_Ok() // Arrange var context = ResilienceContextPool.Shared.Get(); + // Act + var args = new HedgingPredicateArguments(context, Outcome.FromResult(1)); + + // Assert + args.Context.ShouldBe(context); + args.Outcome.Result.ShouldBe(1); + args.AttemptNumber.ShouldBeNull(); + } + + [Fact] + public static void Ctor_With_AttemptNumber_Ok() + { + // Arrange + var context = ResilienceContextPool.Shared.Get(); + // Act var args = new HedgingPredicateArguments(context, Outcome.FromResult(1), 0); From 9c456236344a261e5d2174602513837961cb633a Mon Sep 17 00:00:00 2001 From: Peter Csala Date: Sun, 4 May 2025 12:52:23 +0200 Subject: [PATCH 3/3] Add AttemptNumber to HedgingPredicateArguments --- .../Hedging/HedgingPredicateArguments.cs | 2 +- .../Hedging/HedgingPredicateArgumentsTests.cs | 4 +-- ...esiliencePipelineBuilderExtensionsTests.cs | 36 +++++++++++++++++++ 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/Polly.Core/Hedging/HedgingPredicateArguments.cs b/src/Polly.Core/Hedging/HedgingPredicateArguments.cs index 31bdfd8004e..52b3b26dd22 100644 --- a/src/Polly.Core/Hedging/HedgingPredicateArguments.cs +++ b/src/Polly.Core/Hedging/HedgingPredicateArguments.cs @@ -29,7 +29,7 @@ public HedgingPredicateArguments(ResilienceContext context, Outcome out /// The outcome of the resilience operation or event. /// The zero-based attempt number. public HedgingPredicateArguments(ResilienceContext context, Outcome outcome, int attemptNumber) - : this(context, outcome) => AttemptNumber = attemptNumber; + : this(context, outcome) => AttemptNumber = attemptNumber; /// /// Gets the outcome of the user-specified callback. diff --git a/test/Polly.Core.Tests/Hedging/HedgingPredicateArgumentsTests.cs b/test/Polly.Core.Tests/Hedging/HedgingPredicateArgumentsTests.cs index ab6aefc4471..eb054df83d2 100644 --- a/test/Polly.Core.Tests/Hedging/HedgingPredicateArgumentsTests.cs +++ b/test/Polly.Core.Tests/Hedging/HedgingPredicateArgumentsTests.cs @@ -26,11 +26,11 @@ public static void Ctor_With_AttemptNumber_Ok() var context = ResilienceContextPool.Shared.Get(); // Act - var args = new HedgingPredicateArguments(context, Outcome.FromResult(1), 0); + var args = new HedgingPredicateArguments(context, Outcome.FromResult(1), 10); // Assert args.Context.ShouldBe(context); args.Outcome.Result.ShouldBe(1); - args.AttemptNumber.ShouldBe(0); + args.AttemptNumber.ShouldBe(10); } } diff --git a/test/Polly.Core.Tests/Hedging/HedgingResiliencePipelineBuilderExtensionsTests.cs b/test/Polly.Core.Tests/Hedging/HedgingResiliencePipelineBuilderExtensionsTests.cs index d30f6dfa6f4..0774a6dd4be 100644 --- a/test/Polly.Core.Tests/Hedging/HedgingResiliencePipelineBuilderExtensionsTests.cs +++ b/test/Polly.Core.Tests/Hedging/HedgingResiliencePipelineBuilderExtensionsTests.cs @@ -91,4 +91,40 @@ public async Task AddHedging_IntegrationTestWithRealDelay() var result = await strategy.ExecuteAsync(token => new ValueTask("error")); result.ShouldBe("success"); } + + [Fact] + public async Task AddHedging_AttemptNumbers_Are_Incremented() + { + const string Error = "error"; + const string Success = "success"; + int shouldHandleAttemptNumber = 0; + int actionGeneratorAttemptNumber = 1; + + var strategy = _builder.AddHedging(new() + { + MaxHedgedAttempts = 4, + ShouldHandle = args => + { + args.AttemptNumber.ShouldBe(shouldHandleAttemptNumber++); + return args.Outcome.Result switch + { + Error => PredicateResult.True(), + _ => PredicateResult.False() + }; + }, + ActionGenerator = args => + { + args.AttemptNumber.ShouldBe(actionGeneratorAttemptNumber++); + return async () => + { + await Task.Delay(20); + return Outcome.FromResult(args.AttemptNumber == 3 ? Success : Error); + }; + } + }) + .Build(); + + var result = await strategy.ExecuteAsync(token => new ValueTask(Error)); + result.ShouldBe(Success); + } }