Skip to content
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

feat: Use recommended retries for token and IAM sign blob endpoints. #2879

Merged
merged 11 commits into from
Nov 14, 2024

Conversation

amanda-tarafa
Copy link
Contributor

Closes b/332955964 and b/368412308

@jskeet , as always, one commit at a time.

@amanda-tarafa amanda-tarafa requested a review from jskeet November 13, 2024 08:18
@amanda-tarafa amanda-tarafa requested a review from a team as a code owner November 13, 2024 08:18
Copy link
Collaborator

@jskeet jskeet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've reviewed the first 6 commits - the comment on the 7th doesn't indicate that I've reviewed much of it yet! I think it's fine to add a single commit to fix typos etc - no need to move the change into the relevant original commit. Will try to get to the rest of the PR this time tomorrow.

@@ -28,48 +28,45 @@ public class ExponentialBackOff : IBackOff
/// <summary>The maximum allowed number of retries.</summary>
private const int MaxAllowedNumRetries = 20;

private readonly TimeSpan deltaBackOff;
/// <summary>The random instance which generates a random number to add the to next back-off.</summary>
private static readonly Random Random = new Random();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Random isn't thread-safe - at least in some implementations. (I think it may be in .NET 8+but that's only a vague memory.)

We should probably lock on every use of it.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I've just noticed that this isn't actually introducing a new Random, just moving it - but the comment still stands... we should probably fix that in a separate PR.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, I added a commit at the end.

Starting on .NET 6 there's Random.Shared, but we can't use that at the moment.

/// <summary>
/// Percent of the current unjittered back-off used to bound the jitter.
/// If the value is set to 10, and the current back-off is 1000ms, then the jitter will be generated
/// from [-100, 100]ms and the jittered back-off will be [900,1100]ms.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd change this to "-100ms to 100ms" or similar, to avoid anything thinking it may be markdown. (Ditto the later part.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, done here and in all other places in this file.

/// Constructs a new exponential back-off with the given delta percent and maximum retries.
/// If unspecified, <paramref name="maximumNumOfRetries"/> is set to 10.
/// </summary>
public ExponentialBackOff(short deltaBackOffPercent, int maximumNumOfRetries = 10)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be tempted to make this a factory method with a private constructor - something taking "two integers" could end up getting confusing if we add more overloads later.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, good idea.

(int)(DeltaBackOff.TotalMilliseconds * 1));
int backOffMilli = (int)(Math.Pow(2.0, (double)currentRetry - 1) * 1000 + randomMilli);
return TimeSpan.FromMilliseconds(backOffMilli);
double unjitterdBackOffMs = Math.Pow(2.0, currentRetry - 1) * 1000;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: unjitterd => unjittered (or possibly "raw"?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Raw seems better, done.

private int GetPercentBoundedJitter(double unjitterdBackOffMs) =>
DeltaBackOffPercent == 0 ?
0 :
GetRandomBoundedValue((int)(DeltaBackOffPercent * unjitterdBackOffMs)/100);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: space before and after the /

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@@ -50,6 +57,8 @@ public class ExponentialBackOffInitializer : IConfigurableHttpClientInitializer

/// <summary>
/// Constructs a new back-off initializer with the given policy and back-off handler create function.
/// If <paramref name="policy"/> has the <see cref="ExponentialBackOffPolicy.RecommendedOrDefault"/> flag set,
/// the <see cref="BackOffHandler"/> will be set for handling both excetions and HTTP Status codes.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

excetions => exceptions

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

/// <summary>
/// Retry strategy recommended for the OAuth2 token endpoints.
/// </summary>
internal static readonly ExponentialBackOffInitializer OAuth2TokenEndpointRecommendedRetry = new ExponentialBackOffInitializer(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest making this a property instead of an internal field. (Just by adding { get; })

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, yes

HandleExceptionFunc = ex => false,
HandleUnsuccessfulResponseFunc = response =>
response.StatusCode == HttpStatusCode.RequestTimeout || // 408
(int) response.StatusCode == 429 || // 429 Enum value not availbale in .NET Standard 2.0 or .NET Framework 4.6.2
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

availbale => available

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And add a comment saying what the enum value would be - I don't know what 429 means offhand.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both done

/// <see cref="ExponentialBackOffPolicy.UnsuccessfulResponse503"/> flags. Otherwise, the returned value will be <paramref name="originalPolicy"/>.
/// This method facilitate honoring any custom policies that may have been set. Calling code is resposible for honoring the recommended policies.
/// </summary>
internal static ExponentialBackOffPolicy StripOAuth2TokenEndpointRecommendedPolicy(ExponentialBackOffPolicy originalPolicy) =>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm very glad this is internal, as it feels a bit odd to me. I'm not quite sure what the purpose is yet, but I suspect I'll understand later.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We just don't have enums for configuring retries, they are also Flags.

The recommended OAuth Token endpoint retry includes retrying on 503, but not on exceptions. So, if the original enum value has RecommendedOrDefault set we don't need to add a specific policy for 503. We only keep here what's not recommended, in that way, calling code know whether it needs to add a policy different than recommended.
Calling code also needs to check if it needs to add the recommended policy or not.

Happy to chat about this though. I had several different approaches for this, but ended up preferring this one.

@@ -40,6 +39,97 @@ public class ServiceAccountCredentialTests
private static readonly TimeSpan JwtLifetime = TimeSpan.FromMinutes(60);
private static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

[Fact]
public void Deafault_RecommendedRetryPolicy()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deafault => Default

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Collaborator

@jskeet jskeet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I think I understand all of this now. I'm sure we'd have represented backoffs in a different (non-enum-based) way if we got to redesign it from scratch, but this seems fine as a way of fitting in with that design :)

@@ -81,4 +81,59 @@ public void StripOAuth2TokenEndpointRecommendedPolicy_Strips(ExponentialBackOffP
var stripped = GoogleAuthConsts.StripOAuth2TokenEndpointRecommendedPolicy(original);
Assert.Equal(expected, stripped);
}

[Fact]
public void IamSingBlobTokenEndpointRecommendedRetry()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sing => Sign

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Contributor Author

@amanda-tarafa amanda-tarafa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All comments addressed in sorted review commits (it was not hard this time to do that).
Plus, and extra commit at the end to lock on random.

Thanks!

@@ -28,48 +28,45 @@ public class ExponentialBackOff : IBackOff
/// <summary>The maximum allowed number of retries.</summary>
private const int MaxAllowedNumRetries = 20;

private readonly TimeSpan deltaBackOff;
/// <summary>The random instance which generates a random number to add the to next back-off.</summary>
private static readonly Random Random = new Random();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, I added a commit at the end.

Starting on .NET 6 there's Random.Shared, but we can't use that at the moment.

/// <summary>
/// Percent of the current unjittered back-off used to bound the jitter.
/// If the value is set to 10, and the current back-off is 1000ms, then the jitter will be generated
/// from [-100, 100]ms and the jittered back-off will be [900,1100]ms.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, done here and in all other places in this file.

/// Constructs a new exponential back-off with the given delta percent and maximum retries.
/// If unspecified, <paramref name="maximumNumOfRetries"/> is set to 10.
/// </summary>
public ExponentialBackOff(short deltaBackOffPercent, int maximumNumOfRetries = 10)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, good idea.

(int)(DeltaBackOff.TotalMilliseconds * 1));
int backOffMilli = (int)(Math.Pow(2.0, (double)currentRetry - 1) * 1000 + randomMilli);
return TimeSpan.FromMilliseconds(backOffMilli);
double unjitterdBackOffMs = Math.Pow(2.0, currentRetry - 1) * 1000;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Raw seems better, done.

private int GetPercentBoundedJitter(double unjitterdBackOffMs) =>
DeltaBackOffPercent == 0 ?
0 :
GetRandomBoundedValue((int)(DeltaBackOffPercent * unjitterdBackOffMs)/100);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

/// <summary>
/// Retry strategy recommended for the OAuth2 token endpoints.
/// </summary>
internal static readonly ExponentialBackOffInitializer OAuth2TokenEndpointRecommendedRetry = new ExponentialBackOffInitializer(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, yes

HandleExceptionFunc = ex => false,
HandleUnsuccessfulResponseFunc = response =>
response.StatusCode == HttpStatusCode.RequestTimeout || // 408
(int) response.StatusCode == 429 || // 429 Enum value not availbale in .NET Standard 2.0 or .NET Framework 4.6.2
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both done

/// <see cref="ExponentialBackOffPolicy.UnsuccessfulResponse503"/> flags. Otherwise, the returned value will be <paramref name="originalPolicy"/>.
/// This method facilitate honoring any custom policies that may have been set. Calling code is resposible for honoring the recommended policies.
/// </summary>
internal static ExponentialBackOffPolicy StripOAuth2TokenEndpointRecommendedPolicy(ExponentialBackOffPolicy originalPolicy) =>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We just don't have enums for configuring retries, they are also Flags.

The recommended OAuth Token endpoint retry includes retrying on 503, but not on exceptions. So, if the original enum value has RecommendedOrDefault set we don't need to add a specific policy for 503. We only keep here what's not recommended, in that way, calling code know whether it needs to add a policy different than recommended.
Calling code also needs to check if it needs to add the recommended policy or not.

Happy to chat about this though. I had several different approaches for this, but ended up preferring this one.

@@ -40,6 +39,97 @@ public class ServiceAccountCredentialTests
private static readonly TimeSpan JwtLifetime = TimeSpan.FromMinutes(60);
private static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

[Fact]
public void Deafault_RecommendedRetryPolicy()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@@ -81,4 +81,59 @@ public void StripOAuth2TokenEndpointRecommendedPolicy_Strips(ExponentialBackOffP
var stripped = GoogleAuthConsts.StripOAuth2TokenEndpointRecommendedPolicy(original);
Assert.Equal(expected, stripped);
}

[Fact]
public void IamSingBlobTokenEndpointRecommendedRetry()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Collaborator

@jskeet jskeet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All good!

@amanda-tarafa
Copy link
Contributor Author

Thanks, rebasing the review commits and merging after.

@amanda-tarafa amanda-tarafa merged commit 3d5804a into googleapis:main Nov 14, 2024
4 checks passed
@amanda-tarafa amanda-tarafa deleted the retries branch November 14, 2024 06:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants