-
-
Couldn't load subscription status.
- Fork 1.3k
Description
Describe the bug
If I cancel an execution that has been handled by a retry policy, it may or may not return the last executed outcome.
Expected behavior
There is some question as to what should happen when you trigger cancellation for a resilience strategy that has already invoked the callback but has more work to do. On the one hand, the caller may want to know that execution did not complete per configuration (esp. if the intent is to retry indefinitely). On the other, the invocation may have had side effects, and we do have an outcome that the caller might be interested in.
In my opinion, callbacks passed to resilience pipelines shouldn't make assumptions about how they will be executed and should include a catch to clean up any side-effects. Therefore, a retry strategy that would have attempted additional invocations should acknowledge cancellation by throwing OperationCanceledException regardless of whether it has executed the callback or what delay configuration is in place.
However, if instead we want the retry strategy to return the last executed outcome, it should do so consistently and refrain from acknowledging cancellation if invocation has completed at least once.
Actual behavior
If the cancellation token is in the requested state at the time an invocation of the callback completes, that outcome is returned to the caller. If the token is instead triggered during the subsequent delay between attempts, an OperationCanceledException outcome is returned.
Steps to reproduce
using Polly;
var pipeline = new ResiliencePipelineBuilder<int>()
.AddRetry(
new()
{
ShouldHandle = x => ValueTask.FromResult(x.Outcome.Result < 10),
MaxRetryAttempts = 10,
Delay = TimeSpan.FromSeconds(2),
})
.Build();
await TestCancel(TimeSpan.FromSeconds(1));
await TestCancel(TimeSpan.FromSeconds(3));
await TestCancel(TimeSpan.FromSeconds(5));
await TestCancel(TimeSpan.FromSeconds(7));
async Task TestCancel(TimeSpan duration)
{
try
{
using var cancellation = new CancellationTokenSource(duration);
var n = 0;
var result = await pipeline.ExecuteAsync(
async cancellationToken =>
{
Console.WriteLine($"Attempt: {n}");
await Task.Delay(2000, CancellationToken.None);
return n++;
},
cancellation.Token);
Console.WriteLine($"Execution completed: {result}");
}
catch (OperationCanceledException)
{
Console.WriteLine("Execution was canceled.");
}
}Attempt: 0
Execution completed: 0
Attempt: 0
Execution was canceled.
Attempt: 0
Attempt: 1
Execution completed: 1
Attempt: 0
Attempt: 1
Execution was canceled.
Exception(s) (if any)
No response
Polly version
8.4.2
.NET Version
8.0.403
Anything else?
No response