-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Handle CancellationToken for Retry #2396
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -53,6 +53,11 @@ protected internal override async ValueTask<Outcome<T>> ExecuteCore<TState>(Func | |
{ | ||
var startTimestamp = _timeProvider.GetTimestamp(); | ||
var outcome = await StrategyHelper.ExecuteCallbackSafeAsync(callback, context, state).ConfigureAwait(context.ContinueOnCapturedContext); | ||
if (context.CancellationToken.IsCancellationRequested) | ||
{ | ||
outcome = Outcome.FromException<T>(new OperationCanceledException(context.CancellationToken)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it would be better to acknowledge cancellation only when the retry policy would handle the outcome. It's important that a valid outcome be surfaced by the policy, since the choice to continue processing may well have been deliberate (cf. item three of MS recommendations). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Before this PR:
This PR handles the following case:
Just to clarify: are you asking to
Is my understanding correct? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's correct. Only a handled outcome that is not the last outcome should throw, since this is the only scenario where more work remains to be done by the policy. Under no circumstances should a handled outcome that is not the last outcome be returned (as is currently possible). |
||
} | ||
|
||
var shouldRetryArgs = new RetryPredicateArguments<T>(context, outcome, attempt); | ||
var handle = await ShouldHandle(shouldRetryArgs).ConfigureAwait(context.ContinueOnCapturedContext); | ||
var executionTime = _timeProvider.GetElapsedTime(startTimestamp); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One thing to be aware. If the outcome with disposable result is produced and then you are replacing it with exception, it might lead to a memory leak.
I think it might be good idea to encapsulate all this handling into
ExecuteCallbackSafeAsync
method.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we would move the code inside the
ExecuteCallbackSafeAsync
then we don't have access to theisLastAttempt
. @kmcclellan suggested to respect the cancellation request only under certain circumstances.Please see the other comment section for more details.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
ExecuteCallbackSafeAsync
is internal, we could pass that information to the function. Wdyt?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That function is used by almost all strategies, where the "retry" concept may or may not make any sense. Of course we can pass true as a default for
isLastAttempt
but it feels a bit awkward to me.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you still suggesting to add a new
isLastAttempt
parameter with default value? I'm fine with either approach, I just want to close this PR this year if possible 😁There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aaah sorry about this, got busy with other stuff. I would say meh, we can add it, it's internal detail anyway. At least this logic will be encapsulated there.
So a new default value with
isLastAttempt = true
. Retry strategy will provide its own value.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi! I am the reporter of the bug. Hoping I can help move this forward to the right solution!
The concern about disposable outcomes being thrown away is not really relevant to this change. Abiding by the rules of cancellation, we should not replace an outcome unless we have more work to do (in which case it was probably an exception and won't have a result to dispose).
It is only handled outcomes that are not the last outcome which could have a disposable result thrown away. This is true whether we attempt another execution or acknowledge cancellation. It's fair to say that retry policies currently don't support handling disposable outcomes.
I'm not even sure what
ExecuteCallbackSafeAsync
would supposedly do withisLastAttempt
. In order to dispose, it would also need to know that the strategy would handle the outcome.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The strategy requires the operation's outcome to decide whether to perform a retry attempt or not.
If we want to respect the cancellation request after the strategy has decided to handle the outcome or not and whether this was a last attempt or not then we have the following situation:
ShouldHandle
's args receives the outcome of the operationThat means inside the
ShouldHandle
delegate users would see the operation's outcome but inside the telemetry and at theExecute{Async}
's result they might see an OCE. This also feels a bit inconsistent behavior for me.