Skip to content

Commit 03fa4d1

Browse files
authored
Merge pull request #515 from microsoft/release/update/250611105136
Added UseExponentialRetryDelayForConcurrencyThrottle configuration option
2 parents e5c182b + bfdf9f8 commit 03fa4d1

File tree

6 files changed

+56
-6
lines changed

6 files changed

+56
-6
lines changed

src/GeneralTools/DataverseClient/Client/ConnectionService.cs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2475,14 +2475,19 @@ private bool ShouldRetryWebAPI(Exception ex, int retryCount, int maxRetryCount,
24752475
errorCode == ((int)ErrorCodes.ThrottlingTimeExceededError).ToString() ||
24762476
errorCode == ((int)ErrorCodes.ThrottlingConcurrencyLimitExceededError).ToString())
24772477
{
2478-
if (httpOperationException.Response.Headers.TryGetValue("Retry-After", out var retryAfter) && double.TryParse(retryAfter.FirstOrDefault(), out var retrySeconds))
2479-
{
2478+
if (_configuration.Value.UseExponentialRetryDelayForConcurrencyThrottle && errorCode == ((int)ErrorCodes.ThrottlingConcurrencyLimitExceededError).ToString())
2479+
{
2480+
// Use exponential delay for concurrency throttling if UseExponentialRetryDelayForConcurrencyThrottle is set to true
2481+
_retryPauseTimeRunning = retryPauseTime.Add(TimeSpan.FromSeconds(Math.Pow(2, retryCount)));
2482+
}
2483+
else if (httpOperationException.Response.Headers.TryGetValue("Retry-After", out var retryAfter) && double.TryParse(retryAfter.FirstOrDefault(), out var retrySeconds))
2484+
{
24802485
// Note: Retry-After header is in seconds.
2481-
_retryPauseTimeRunning = TimeSpan.FromSeconds(retrySeconds);
2486+
_retryPauseTimeRunning = TimeSpan.FromSeconds(retrySeconds);
24822487
}
24832488
else
24842489
{
2485-
_retryPauseTimeRunning = retryPauseTime.Add(TimeSpan.FromSeconds(Math.Pow(2, retryCount))); ; // default timespan with back off is response does not contain the tag..
2490+
_retryPauseTimeRunning = retryPauseTime.Add(TimeSpan.FromSeconds(Math.Pow(2, retryCount))); // default timespan with back off is response does not contain the tag..
24862491
}
24872492
isThrottlingRetry = true;
24882493
return true;
@@ -2595,6 +2600,7 @@ private bool ShouldRetryWebAPI(Exception ex, int retryCount, int maxRetryCount,
25952600
}
25962601
}
25972602
catch { }
2603+
// CodeQL [SM03781] By Design - this is a client library and users need to be able to target any relevant URI
25982604
_httpResponse = await providedHttpClient.SendAsync(_httpRequest, cancellationToken).ConfigureAwait(false);
25992605
logDt.Stop();
26002606
}
@@ -2613,6 +2619,7 @@ private bool ShouldRetryWebAPI(Exception ex, int retryCount, int maxRetryCount,
26132619
}
26142620
}
26152621
catch { }
2622+
// CodeQL [SM03781] By Design - this is a client library and users need to be able to target any relevant URI
26162623
_httpResponse = await httpCli.SendAsync(_httpRequest, cancellationToken).ConfigureAwait(false);
26172624
logDt.Stop();
26182625
}

src/GeneralTools/DataverseClient/Client/Model/ConfigurationOptions.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public void UpdateOptions(ConfigurationOptions options)
3333
RetryPauseTime = options.RetryPauseTime;
3434
UseWebApi = options.UseWebApi;
3535
UseWebApiLoginFlow = options.UseWebApiLoginFlow;
36+
UseExponentialRetryDelayForConcurrencyThrottle = options.UseExponentialRetryDelayForConcurrencyThrottle;
3637
}
3738
}
3839

@@ -71,6 +72,17 @@ public bool UseWebApi
7172
set => _useWebApi = value;
7273
}
7374

75+
private bool _useExponentialRetryDelayForConcurrencyThrottle = Utils.AppSettingsHelper.GetAppSetting<bool>("UseExponentialRetryDelayForConcurrencyThrottle", false);
76+
77+
/// <summary>
78+
/// Use exponential retry delay for concurrency throttling instead of server specified Retry-After header
79+
/// </summary>
80+
public bool UseExponentialRetryDelayForConcurrencyThrottle
81+
{
82+
get => _useExponentialRetryDelayForConcurrencyThrottle;
83+
set => _useExponentialRetryDelayForConcurrencyThrottle = value;
84+
}
85+
7486
private bool _useWebApiLoginFlow = Utils.AppSettingsHelper.GetAppSetting<bool>("UseWebApiLoginFlow", true);
7587
/// <summary>
7688
/// Use Web API instead of org service for logging into and getting boot up data.

src/GeneralTools/DataverseClient/Client/ServiceClient.cs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,15 @@ public TimeSpan RetryPauseTime
207207
set { _configuration.Value.RetryPauseTime = value; }
208208
}
209209

210+
/// <summary>
211+
/// Use exponential retry delay for concurrency throttling instead of server specified Retry-After header where possible - Defaults to False.
212+
/// </summary>
213+
public bool UseExponentialRetryDelayForConcurrencyThrottle
214+
{
215+
get { return _configuration.Value.UseExponentialRetryDelayForConcurrencyThrottle; }
216+
set { _configuration.Value.UseExponentialRetryDelayForConcurrencyThrottle = value; }
217+
}
218+
210219
/// <summary>
211220
/// if true the service is ready to accept requests.
212221
/// </summary>
@@ -1417,6 +1426,7 @@ public ServiceClient Clone(System.Reflection.Assembly strongTypeAsm, ILogger log
14171426
SvcClient.CallerId = CallerId;
14181427
SvcClient.MaxRetryCount = _configuration.Value.MaxRetryCount;
14191428
SvcClient.RetryPauseTime = _configuration.Value.RetryPauseTime;
1429+
SvcClient.UseExponentialRetryDelayForConcurrencyThrottle = _configuration.Value.UseExponentialRetryDelayForConcurrencyThrottle;
14201430
SvcClient.GetAccessToken = GetAccessToken;
14211431
SvcClient.GetCustomHeaders = GetCustomHeaders;
14221432
return SvcClient;
@@ -2055,9 +2065,14 @@ private bool ShouldRetry(OrganizationRequest req, Exception ex, int retryCount,
20552065
OrgEx.Detail.ErrorCode == ErrorCodes.ThrottlingTimeExceededError ||
20562066
OrgEx.Detail.ErrorCode == ErrorCodes.ThrottlingConcurrencyLimitExceededError)
20572067
{
2058-
// Use Retry-After delay when specified
2059-
if (OrgEx.Detail.ErrorDetails.TryGetValue("Retry-After", out var retryAfter) && retryAfter is TimeSpan retryAsTimeSpan)
2068+
if (_configuration.Value.UseExponentialRetryDelayForConcurrencyThrottle && OrgEx.Detail.ErrorCode == ErrorCodes.ThrottlingConcurrencyLimitExceededError)
2069+
{
2070+
// Use exponential delay for concurrency throttling if UseExponentialRetryDelayForConcurrencyThrottle is set to true
2071+
_retryPauseTimeRunning = _configuration.Value.RetryPauseTime.Add(TimeSpan.FromSeconds(Math.Pow(2, retryCount)));
2072+
}
2073+
else if (OrgEx.Detail.ErrorDetails.TryGetValue("Retry-After", out var retryAfter) && retryAfter is TimeSpan retryAsTimeSpan)
20602074
{
2075+
// Use Retry-After delay when specified
20612076
_retryPauseTimeRunning = retryAsTimeSpan;
20622077
}
20632078
else

src/GeneralTools/DataverseClient/Extensions/Microsoft.PowerPlatform.Dataverse.Client.AzAuth/AzAuth.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ private async Task<Uri> InitializeCredentials(Uri instanceUrl)
190190
_logger.LogDebug("Initialize Creds - found resource with name " + (string.IsNullOrEmpty(authDetails.Resource.ToString()) ? "<Not Provided>" : authDetails.Resource.ToString()));
191191
_logger.LogDebug("Initialize Creds - found tenantId " + (string.IsNullOrEmpty(_credentialOptions.TenantId) ? "<Not Provided>" : _credentialOptions.TenantId));
192192
}
193+
// CodeQL [SM05137] Not applicable - this is a Public client SDK
193194
_defaultAzureCredential = new DefaultAzureCredential(_credentialOptions);
194195

195196
_logger.LogDebug("Credentials initialized in {0}ms", sw.ElapsedMilliseconds);

src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/ServiceClientTests.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -884,6 +884,18 @@ public async Task RetryOperationShouldNotThrowWhenAlreadyCanceledTest()
884884
testwatch.Elapsed.Should().BeLessThan(delay, "Task should return before its delay timer can complete due to cancellation");
885885
}
886886

887+
[Fact]
888+
public void TestOptionUseExponentialRetryDelayForConcurrencyThrottle()
889+
{
890+
testSupport.SetupMockAndSupport(out var orgSvc, out var fakHttpMethodHander, out var cli);
891+
cli.UseExponentialRetryDelayForConcurrencyThrottle = true;
892+
893+
var rsp = (WhoAmIResponse)cli.ExecuteOrganizationRequest(new WhoAmIRequest());
894+
895+
// Validate that the behavior remains unchanged when the option UseExponentialRetryDelayForConcurrencyThrottle is set to true.
896+
Assert.Equal(rsp.UserId, testSupport._UserId);
897+
}
898+
887899
#region LiveConnectedTests
888900

889901
[SkippableConnectionTest]

src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.ReleaseNotes.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ Notice:
77
Note: Only AD on FullFramework, OAuth, Certificate, ClientSecret Authentication types are supported at this time.
88

99
++CURRENTRELEASEID++
10+
Add a new configuration option UseExponentialRetryDelayForConcurrencyThrottle to use exponential retry delay for concurrency throttling, instead of the server-specified Retry-After header where applicable. Default is False.
11+
12+
1.2.7
1013
Fix for CancellationToken not canceling retries during delays Git: https://github.com/microsoft/PowerPlatform-DataverseServiceClient/issues/508
1114

1215
1.2.5

0 commit comments

Comments
 (0)