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
Throw exceptions in a more consistent way #10353
Changes from all commits
cae0b4d
1a39f5c
0cc30b5
230d848
078d40d
9e7c48a
9ea40dd
0c8e99b
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 |
---|---|---|
@@ -1,4 +1,4 @@ | ||
using Newtonsoft.Json; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Net.Http; | ||
|
@@ -61,9 +61,7 @@ | |
|
||
private async Task<HttpResponseMessage> SendWithRetriesAsync(RemoteAction action, string jsonString, CancellationToken cancellationToken, TimeSpan? retryTimeout = null) | ||
{ | ||
var exceptions = new Dictionary<Exception, int>(); | ||
var start = DateTime.UtcNow; | ||
|
||
var totalTimeout = TimeSpan.FromMinutes(30); | ||
|
||
using CancellationTokenSource absoluteTimeoutCts = new(totalTimeout); | ||
|
@@ -86,10 +84,10 @@ | |
|
||
TimeSpan totalTime = DateTime.UtcNow - start; | ||
|
||
if (exceptions.Any()) | ||
if (attempt > 1) | ||
{ | ||
Logger.LogDebug( | ||
$"Received a response for {action} in {totalTime.TotalSeconds:0.##s} after {attempt} failed attempts: {new AggregateException(exceptions.Keys)}."); | ||
$"Received a response for {action} in {totalTime.TotalSeconds:0.##s} after {attempt} failed attempts."); | ||
} | ||
else if (action != RemoteAction.GetStatus) | ||
{ | ||
|
@@ -101,56 +99,23 @@ | |
catch (HttpRequestException e) | ||
{ | ||
Logger.LogTrace($"Attempt {attempt} to perform '{action}' failed with {nameof(HttpRequestException)}: {e.Message}."); | ||
AddException(exceptions, e); | ||
} | ||
catch (OperationCanceledException e) | ||
{ | ||
Logger.LogTrace($"Attempt {attempt} to perform '{action}' failed with {nameof(OperationCanceledException)}: {e.Message}."); | ||
AddException(exceptions, e); | ||
} | ||
catch (Exception e) | ||
{ | ||
Logger.LogDebug($"Attempt {attempt} to perform '{action}' failed with exception {e}."); | ||
|
||
if (exceptions.Any()) | ||
{ | ||
AddException(exceptions, e); | ||
throw new AggregateException(exceptions.Keys); | ||
} | ||
|
||
throw; | ||
} | ||
|
||
try | ||
{ | ||
// Wait before the next try. | ||
await Task.Delay(250, combinedToken).ConfigureAwait(false); | ||
} | ||
catch (Exception e) | ||
{ | ||
AddException(exceptions, e); | ||
} | ||
// Wait before the next try. | ||
await Task.Delay(250, combinedToken).ConfigureAwait(false); | ||
|
||
attempt++; | ||
} | ||
while (!combinedToken.IsCancellationRequested); | ||
|
||
throw new AggregateException(exceptions.Keys); | ||
} | ||
|
||
private static void AddException(Dictionary<Exception, int> exceptions, Exception e) | ||
{ | ||
bool Predicate(KeyValuePair<Exception, int> x) => e.GetType() == x.Key.GetType() && e.Message == x.Key.Message; | ||
|
||
if (exceptions.Any(Predicate)) | ||
{ | ||
var first = exceptions.First(Predicate); | ||
exceptions[first.Key]++; | ||
} | ||
else | ||
{ | ||
exceptions.Add(e, 1); | ||
} | ||
while (true); | ||
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. What was wrong with while (!combinedToken.IsCancellationRequested); ? Isn't We could use the 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. See L114 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.
The method will just return without throwing an exception.
Same risk as before. If combinedToken is never canceled we end up with an infinite loop.
We do not need to, the max attempts are determined by the timeout. 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. Why this change? 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. (Ok I took some time to submit my review and I can see you all already talked about it) |
||
} | ||
|
||
private async Task<string> SendWithRetriesAsync<TRequest>(RemoteAction action, TRequest request, CancellationToken cancellationToken, TimeSpan? retryTimeout = null) where TRequest : class | ||
|
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.
Not related to the PR but
retryTimeout
naming is questionnable as this variable acts like arequestTimeout
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.
I think it is cleaner thus we have a request and a retry timeout.
retryTimeout
is doing something with retries, but the request could be interpreted as the whole request itself with retries or without? Could be confusing.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.
attemptTimeout
But I agree it's ultra nit.