Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
12e89cc
Retry policies are now pre request instead of per HttpManager.
Robbie-Microsoft Apr 23, 2025
2d3b435
Merge branch 'main' into rginsburg_retry_policy_per_request
Robbie-Microsoft Apr 23, 2025
132ec53
Improved unit test
Robbie-Microsoft Apr 24, 2025
fc7eafa
Addressed some GitHub feedback
Robbie-Microsoft Apr 25, 2025
dc831fe
Implemented GitHub feedback
Robbie-Microsoft Apr 29, 2025
6576722
Fixed broken unit tests, update public API
Robbie-Microsoft Apr 30, 2025
6d89f1c
Undid public API changes
Robbie-Microsoft Apr 30, 2025
a8cdac3
Merge branch 'main' into rginsburg_retry_policy_per_request
Robbie-Microsoft Apr 30, 2025
b68df2c
Fixed broken unit test and clarified public api
Robbie-Microsoft Apr 30, 2025
aab8940
removed comment, adjusted unit test
Robbie-Microsoft Apr 30, 2025
6e9cc07
Merge branch 'main' into rginsburg_retry_policy_per_request
Robbie-Microsoft May 1, 2025
4aedf33
Changed constants to PascalCase
Robbie-Microsoft May 1, 2025
4266a14
Deleted file
Robbie-Microsoft May 1, 2025
2153fc3
Undid file change from main
Robbie-Microsoft May 1, 2025
b82f872
Reworked retry policy functionality. Created IMDS retry policy.
Robbie-Microsoft Apr 11, 2025
8b37d08
Implemented GitHub Feedback
Robbie-Microsoft Apr 14, 2025
abe860f
Created first retry unit test. The rest of the tests will be based of…
Robbie-Microsoft Apr 16, 2025
0eef7c6
Added more ImdsRetryPolicy unit tests. Fixed bug discovered by unit t…
Robbie-Microsoft Apr 16, 2025
9faf287
Finished all ImdsRetryPolicy unit tests
Robbie-Microsoft Apr 17, 2025
128f628
Discovered bugs through unit tests, and fixed them. Improved ImdsRetr…
Robbie-Microsoft Apr 18, 2025
69a157a
Minor improvements to ImdsRetryPolicy tests
Robbie-Microsoft Apr 18, 2025
fe3ac7c
Added option retryAfter header to mockHttpManager. Added more Defauly…
Robbie-Microsoft Apr 18, 2025
e73a7bb
Finished DefaultRetryPolicy unit tests. Edited some ImdsRetryPolicy u…
Robbie-Microsoft Apr 21, 2025
d5f5978
rebased branch from main to rginsburg_retry_policy_per_request
Robbie-Microsoft May 1, 2025
c61c3e6
Implemented final feedback
Robbie-Microsoft May 2, 2025
be1d9e4
Merged in latest
Robbie-Microsoft May 2, 2025
42ea075
Fixed some broken unit tests
Robbie-Microsoft May 2, 2025
6fefdf2
Fixed all broken unit tests and made STS tests 100 times faster
Robbie-Microsoft May 2, 2025
b7e70d6
Merge branch 'main' into rginsburg/rginsburg_retry_policy_per_request
Robbie-Microsoft May 2, 2025
717a44a
Merge branch 'rginsburg/rginsburg_retry_policy_per_request' into rgin…
Robbie-Microsoft May 2, 2025
6671271
whitespace
Robbie-Microsoft May 2, 2025
990847e
Retry policies are now pre request instead of per HttpManager.
Robbie-Microsoft Apr 23, 2025
fc7f97c
Addressed some GitHub feedback
Robbie-Microsoft Apr 25, 2025
b3cdc4b
Implemented GitHub feedback
Robbie-Microsoft Apr 29, 2025
7ccb8c8
Fixed broken unit tests, update public API
Robbie-Microsoft Apr 30, 2025
848987f
Undid public API changes
Robbie-Microsoft Apr 30, 2025
383d318
Fixed broken unit test and clarified public api
Robbie-Microsoft Apr 30, 2025
ae69c1d
removed comment, adjusted unit test
Robbie-Microsoft Apr 30, 2025
f0d98ed
Deleted file
Robbie-Microsoft May 1, 2025
e13e0d8
Undid file change from main
Robbie-Microsoft May 1, 2025
39164fb
Reworked retry policy functionality. Created IMDS retry policy.
Robbie-Microsoft Apr 11, 2025
4cab624
Implemented GitHub Feedback
Robbie-Microsoft Apr 14, 2025
39180d9
Created first retry unit test. The rest of the tests will be based of…
Robbie-Microsoft Apr 16, 2025
d2a01a1
Added more ImdsRetryPolicy unit tests. Fixed bug discovered by unit t…
Robbie-Microsoft Apr 16, 2025
57afaf8
Finished all ImdsRetryPolicy unit tests
Robbie-Microsoft Apr 17, 2025
f629dfc
Discovered bugs through unit tests, and fixed them. Improved ImdsRetr…
Robbie-Microsoft Apr 18, 2025
62c0b09
Minor improvements to ImdsRetryPolicy tests
Robbie-Microsoft Apr 18, 2025
c79f23d
Added option retryAfter header to mockHttpManager. Added more Defauly…
Robbie-Microsoft Apr 18, 2025
25ce41b
Finished DefaultRetryPolicy unit tests. Edited some ImdsRetryPolicy u…
Robbie-Microsoft Apr 21, 2025
b33059c
rebased branch from main to rginsburg_retry_policy_per_request
Robbie-Microsoft May 1, 2025
2487799
Fixed some broken unit tests
Robbie-Microsoft May 2, 2025
eb785d6
Fixed all broken unit tests and made STS tests 100 times faster
Robbie-Microsoft May 2, 2025
f5668c7
whitespace
Robbie-Microsoft May 2, 2025
ec2090d
Merge branch 'rginsburg/imds_retry_policy' of https://github.com/Azur…
Robbie-Microsoft May 2, 2025
4cd60ed
Final adjustment of unit tests
Robbie-Microsoft May 2, 2025
1854cd3
Simplified params for DefaultRetryPolicy
Robbie-Microsoft May 2, 2025
001b32d
Implemented some GitHub feedback
Robbie-Microsoft May 20, 2025
a64391c
Merge branch 'main' into rginsburg/imds_retry_policy
Robbie-Microsoft May 20, 2025
bf41d92
Fixed build messages
Robbie-Microsoft May 21, 2025
d9a2dd3
adjusted unit test delay
Robbie-Microsoft May 21, 2025
f4433c7
Fixed build messages
Robbie-Microsoft May 21, 2025
fb9653a
removed unused import
Robbie-Microsoft May 21, 2025
96a65cc
added temporary logs for debugging CI issues
Robbie-Microsoft May 21, 2025
1be5bc1
edited temporary logs
Robbie-Microsoft May 22, 2025
be8d712
Merge branch 'main' into rginsburg/imds_retry_policy
Robbie-Microsoft May 28, 2025
7c87c30
Created "retry" folder inside of "http" and updated all file paths (#…
Robbie-Microsoft May 28, 2025
01bcb73
Created Production and Test Retry Policy Factories, and adjusted unit…
Robbie-Microsoft Jun 2, 2025
735dc22
Merge branch 'main' into rginsburg/imds_retry_policy
Robbie-Microsoft Jun 2, 2025
5e3ad09
Moved RequestType enum from Constants to IRetryPolicyFactory (#5316)
Robbie-Microsoft Jun 2, 2025
960c9d9
Moved RetryPolicyFactory configuration from Managed Identity Builder …
Robbie-Microsoft Jun 2, 2025
94dfbdd
Removed another piece that was left out in previous committ
Robbie-Microsoft Jun 2, 2025
7b69caa
undid changes in ManagedIdentityBuilder
Robbie-Microsoft Jun 2, 2025
126ccc4
Added RetryPolicyFactory to test mock
Robbie-Microsoft Jun 2, 2025
d92059a
Implemented Neha's feedback
Robbie-Microsoft Jun 4, 2025
1f046dd
Merge branch 'main' into rginsburg/imds_retry_policy
Robbie-Microsoft Jun 4, 2025
05d3d86
Implemented Gladwin's feedback
Robbie-Microsoft Jun 4, 2025
ce6349b
undid changes to .csproj
Robbie-Microsoft Jun 4, 2025
dbb5315
Implemented Travis's feedback
Robbie-Microsoft Jun 4, 2025
fdfdfb7
Moved RequestType enum to it's own file and updated imports
Robbie-Microsoft Jun 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Microsoft.Identity.Client.Core;
using Microsoft.Identity.Client.Extensibility;
using Microsoft.Identity.Client.Http;
using Microsoft.Identity.Client.Http.Retry;
using Microsoft.Identity.Client.Instance;
using Microsoft.Identity.Client.Instance.Discovery;
using Microsoft.Identity.Client.Internal.Broker;
Expand Down Expand Up @@ -124,6 +125,8 @@ public string ClientVersion

public Func<AppTokenProviderParameters, Task<AppTokenProviderResult>> AppTokenProvider;

internal IRetryPolicyFactory RetryPolicyFactory { get; set; }

#region ClientCredentials

// Indicates if claims or assertions are used within the configuration
Expand Down Expand Up @@ -207,6 +210,5 @@ public X509Certificate2 ClientCredentialCertificate
public IDeviceAuthManager DeviceAuthManagerForTest { get; set; }
public bool IsInstanceDiscoveryEnabled { get; internal set; } = true;
#endregion

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
using Microsoft.Identity.Client.Utils;
using Microsoft.IdentityModel.Abstractions;
using Microsoft.Identity.Client.Internal;
using Microsoft.Identity.Client.Http.Retry;

#if SUPPORTS_SYSTEM_TEXT_JSON
using System.Text.Json;
#else
Expand All @@ -31,6 +33,12 @@ public abstract class BaseAbstractApplicationBuilder<T>
internal BaseAbstractApplicationBuilder(ApplicationConfiguration configuration)
{
Config = configuration;

// Ensure the default retry policy factory is set if the test factory was not provided
if (Config.RetryPolicyFactory == null)
{
Config.RetryPolicyFactory = new RetryPolicyFactory();
}
}

internal ApplicationConfiguration Config { get; }
Expand Down Expand Up @@ -227,6 +235,17 @@ public T WithClientVersion(string clientVersion)
return this as T;
}

/// <summary>
/// Internal only: Allows tests to inject a custom retry policy factory.
/// </summary>
/// <param name="factory">The retry policy factory to use.</param>
/// <returns>The builder for chaining.</returns>
internal T WithRetryPolicyFactory(IRetryPolicyFactory factory)
{
Config.RetryPolicyFactory = factory;
return (T)this;
}

internal virtual ApplicationConfiguration BuildConfiguration()
{
ResolveAuthority();
Expand Down
8 changes: 5 additions & 3 deletions src/client/Microsoft.Identity.Client/Http/HttpManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Identity.Client.Core;
using Microsoft.Identity.Client.Http.Retry;

namespace Microsoft.Identity.Client.Http
{
Expand Down Expand Up @@ -110,10 +111,11 @@ public async Task<HttpResponse> SendRequestAsync(
logger.Error("The HTTP request failed. " + exception.Message);
timeoutException = exception;
}

while (!_disableInternalRetries && retryPolicy.PauseForRetry(response, timeoutException, retryCount))
while (!_disableInternalRetries && await retryPolicy.PauseForRetryAsync(response, timeoutException, retryCount, logger).ConfigureAwait(false))
{
logger.Warning($"Retry condition met. Retry count: {retryCount++} after waiting {retryPolicy.DelayInMilliseconds}ms.");
retryCount++;

return await SendRequestAsync(
endpoint,
headers,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Identity.Client.Core;
using Microsoft.Identity.Client.Http.Retry;

namespace Microsoft.Identity.Client.Http
{
Expand Down
17 changes: 0 additions & 17 deletions src/client/Microsoft.Identity.Client/Http/IRetryPolicy.cs

This file was deleted.

37 changes: 0 additions & 37 deletions src/client/Microsoft.Identity.Client/Http/LinearRetryPolicy.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Threading.Tasks;
using Microsoft.Identity.Client.Core;

namespace Microsoft.Identity.Client.Http.Retry
{
class DefaultRetryPolicy : IRetryPolicy
{
// referenced in unit tests
public const int DefaultStsMaxRetries = 1;
public const int DefaultManagedIdentityMaxRetries = 3;

private const int DefaultStsRetryDelayMs = 1000;
private const int DefaultManagedIdentityRetryDelayMs = 1000;

public readonly int _defaultRetryDelayMs;
private readonly int _maxRetries;
private readonly Func<HttpResponse, Exception, bool> _retryCondition;
private readonly LinearRetryStrategy _linearRetryStrategy = new LinearRetryStrategy();

public DefaultRetryPolicy(RequestType requestType)
{
switch (requestType)
{
case RequestType.ManagedIdentityDefault:
_defaultRetryDelayMs = DefaultManagedIdentityRetryDelayMs;
_maxRetries = DefaultManagedIdentityMaxRetries;
_retryCondition = HttpRetryConditions.DefaultManagedIdentity;
break;
case RequestType.STS:
_defaultRetryDelayMs = DefaultStsRetryDelayMs;
_maxRetries = DefaultStsMaxRetries;
_retryCondition = HttpRetryConditions.Sts;
break;
default:
throw new ArgumentOutOfRangeException(nameof(requestType), requestType, "Unknown request type");
}
}

internal virtual Task DelayAsync(int milliseconds)
{
return Task.Delay(milliseconds);
}

public async Task<bool> PauseForRetryAsync(HttpResponse response, Exception exception, int retryCount, ILoggerAdapter logger)
{
// Check if the status code is retriable and if the current retry count is less than max retries
if (_retryCondition(response, exception) &&
retryCount < _maxRetries)
{
// Use HeadersAsDictionary to check for "Retry-After" header
string retryAfter = string.Empty;
if (response?.HeadersAsDictionary != null)
{
response.HeadersAsDictionary.TryGetValue("Retry-After", out retryAfter);
}

int retryAfterDelay = _linearRetryStrategy.CalculateDelay(retryAfter, _defaultRetryDelayMs);

logger.Warning($"Retrying request in {retryAfterDelay}ms (retry attempt: {retryCount + 1})");

// Pause execution for the calculated delay
await DelayAsync(retryAfterDelay).ConfigureAwait(false);

return true;
}

// If the status code is not retriable or max retries have been reached, do not retry
return false;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;

namespace Microsoft.Identity.Client.Http.Retry
{
internal class ExponentialRetryStrategy
{
// Minimum backoff time in milliseconds
private int _minExponentialBackoff;
// Maximum backoff time in milliseconds
private int _maxExponentialBackoff;
// Maximum backoff time in milliseconds
private int _exponentialDeltaBackoff;

public ExponentialRetryStrategy(int minExponentialBackoff, int maxExponentialBackoff, int exponentialDeltaBackoff)
{
_minExponentialBackoff = minExponentialBackoff;
_maxExponentialBackoff = maxExponentialBackoff;
_exponentialDeltaBackoff = exponentialDeltaBackoff;
}

/// <summary>
/// Calculates the exponential delay based on the current retry attempt.
/// </summary>
/// <param name="currentRetry">The current retry attempt number.</param>
/// <returns>The calculated exponential delay in milliseconds.</returns>
/// <remarks>
/// The delay is calculated using the formula:
/// - If <paramref name="currentRetry"/> is 0, it returns the minimum backoff time.
/// - Otherwise, it calculates the delay as the minimum of:
/// - (2^(currentRetry - 1)) * deltaBackoff
/// - maxBackoff
/// This ensures that the delay increases exponentially with each retry attempt,
/// but does not exceed the maximum backoff time.
/// </remarks>
public int CalculateDelay(int currentRetry)
{
// Attempt 1
if (currentRetry == 0)
{
return _minExponentialBackoff;
}

// Attempt 2+
int exponentialDelay = Math.Min(
(int)(Math.Pow(2, currentRetry - 1) * _exponentialDeltaBackoff),
_maxExponentialBackoff
);

return exponentialDelay;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@
using System;
using System.Threading.Tasks;

namespace Microsoft.Identity.Client.Http
namespace Microsoft.Identity.Client.Http.Retry
{
internal static class HttpRetryConditions
{
/// <summary>
/// Retry policy specific to managed identity flow.
/// Avoid changing this, as it's breaking change.
/// Avoid changing this, as it's a breaking change.
/// </summary>
public static bool ManagedIdentity(HttpResponse response, Exception exception)
public static bool DefaultManagedIdentity(HttpResponse response, Exception exception)
{
if (exception != null)
{
Expand All @@ -21,12 +21,32 @@ public static bool ManagedIdentity(HttpResponse response, Exception exception)

return (int)response.StatusCode switch
{
//Not Found
// Not Found, Request Timeout, Too Many Requests, Server Error, Service Unavailable, Gateway Timeout
404 or 408 or 429 or 500 or 503 or 504 => true,
_ => false,
};
}

/// <summary>
/// Retry policy specific to IMDS Managed Identity.
/// </summary>
public static bool Imds(HttpResponse response, Exception exception)
{
if (exception != null)
{
return exception is TaskCanceledException ? true : false;
}

return (int)response.StatusCode switch
{
// Not Found, Request Timeout, Gone, Too Many Requests
404 or 408 or 410 or 429 => true,
// Server Error range
>= 500 and <= 599 => true,
_ => false,
};
}

/// <summary>
/// Retry condition for /token and /authorize endpoints
/// </summary>
Expand Down
25 changes: 25 additions & 0 deletions src/client/Microsoft.Identity.Client/Http/Retry/IRetryPolicy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Threading.Tasks;
using Microsoft.Identity.Client.Core;

namespace Microsoft.Identity.Client.Http.Retry
{
// Interface for implementing retry logic for HTTP requests.
// Determines if a retry should occur and handles pause logic between retries.
internal interface IRetryPolicy
{
/// <summary>
/// Determines whether a retry should be attempted for a given HTTP response or exception,
/// and performs any necessary pause or delay logic before the next retry attempt.
/// </summary>
/// <param name="response">The HTTP response received from the request.</param>
/// <param name="exception">The exception encountered during the request.</param>
/// <param name="retryCount">The current retry attempt count.</param>
/// <param name="logger">The logger used for diagnostic and informational messages.</param>
/// <returns>A task that returns true if a retry should be performed; otherwise, false.</returns>
Task<bool> PauseForRetryAsync(HttpResponse response, Exception exception, int retryCount, ILoggerAdapter logger);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

namespace Microsoft.Identity.Client.Http.Retry
{
internal interface IRetryPolicyFactory
{
IRetryPolicy GetRetryPolicy(RequestType requestType);
}
}
Loading