diff --git a/src/Polly.Core/Hedging/HedgingResilienceStrategy.cs b/src/Polly.Core/Hedging/HedgingResilienceStrategy.cs index d497271ac1c..d96e0197d90 100644 --- a/src/Polly.Core/Hedging/HedgingResilienceStrategy.cs +++ b/src/Polly.Core/Hedging/HedgingResilienceStrategy.cs @@ -67,12 +67,16 @@ private async ValueTask> ExecuteCoreAsync( ResilienceContext context, TState state) { + // Capture the original cancellation token so it stays the same while hedging is executing. + // If we do not do this the inner strategy can replace the cancellation token and with the concurrent + // nature of hedging this can cause issues. + var cancellationToken = context.CancellationToken; + var continueOnCapturedContext = context.ContinueOnCapturedContext; + var attempt = -1; while (true) { attempt++; - var continueOnCapturedContext = context.ContinueOnCapturedContext; - var cancellationToken = context.CancellationToken; var start = _timeProvider.GetTimestamp(); if (cancellationToken.IsCancellationRequested) { diff --git a/test/Polly.Core.Tests/Hedging/HedgingResilienceStrategyTests.cs b/test/Polly.Core.Tests/Hedging/HedgingResilienceStrategyTests.cs index 3119ff42a05..663067abc7e 100644 --- a/test/Polly.Core.Tests/Hedging/HedgingResilienceStrategyTests.cs +++ b/test/Polly.Core.Tests/Hedging/HedgingResilienceStrategyTests.cs @@ -104,6 +104,28 @@ public void ExecutePrimaryAndSecondary_EnsureAttemptReported() attempts[1].Attempt.Should().Be(1); } + [Fact] + public async Task ExecutePrimary_Cancelled_SecondaryShouldBeExecuted() + { + _options.MaxHedgedAttempts = 2; + + ConfigureHedging(o => o.Result == "primary", args => () => Outcome.FromResultAsTask("secondary")); + var strategy = Create(); + + var result = await strategy.ExecuteAsync( + context => + { + var source = new CancellationTokenSource(); + source.Cancel(); + context.CancellationToken = source.Token; + + return new ValueTask("primary"); + }, + ResilienceContext.Get()); + + result.Should().Be("secondary"); + } + [InlineData(-1)] [InlineData(-1000)] [InlineData(0)]