Skip to content

Commit 85dbb19

Browse files
authored
Dts Grpc Client Retry Support (#403)
1 parent e32fa7a commit 85dbb19

File tree

5 files changed

+123
-2
lines changed

5 files changed

+123
-2
lines changed

src/Client/AzureManaged/DurableTaskSchedulerClientOptions.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using Azure.Identity;
77
using Grpc.Core;
88
using Grpc.Net.Client;
9+
using Grpc.Net.Client.Configuration;
910

1011
namespace Microsoft.DurableTask;
1112

@@ -112,6 +113,7 @@ this.Credential is not null
112113
{
113114
Credentials = ChannelCredentials.Create(channelCreds, managedBackendCreds),
114115
UnsafeUseInsecureChannelCallCredentials = this.AllowInsecureCredentials,
116+
ServiceConfig = GrpcRetryPolicyDefaults.DefaultServiceConfig,
115117
});
116118
}
117119

@@ -127,7 +129,7 @@ this.Credential is not null
127129
case "managedidentity":
128130
return new ManagedIdentityCredential(connectionString.ClientId);
129131
case "workloadidentity":
130-
var opts = new WorkloadIdentityCredentialOptions();
132+
WorkloadIdentityCredentialOptions opts = new WorkloadIdentityCredentialOptions();
131133
if (!string.IsNullOrEmpty(connectionString.ClientId))
132134
{
133135
opts.ClientId = connectionString.ClientId;
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using Grpc.Core;
5+
using Grpc.Net.Client.Configuration;
6+
7+
namespace Microsoft.DurableTask;
8+
9+
/// <summary>
10+
/// Provides default retry policy configurations for gRPC client connections.
11+
/// </summary>
12+
sealed class GrpcRetryPolicyDefaults
13+
{
14+
/// <summary>
15+
/// Default maximum number of retry attempts.
16+
/// </summary>
17+
public const int DefaultMaxAttempts = 10;
18+
19+
/// <summary>
20+
/// Default initial backoff in milliseconds.
21+
/// </summary>
22+
public const int DefaultInitialBackoffMs = 50;
23+
24+
/// <summary>
25+
/// Default maximum backoff in milliseconds.
26+
/// </summary>
27+
public const int DefaultMaxBackoffMs = 250;
28+
29+
/// <summary>
30+
/// Default backoff multiplier for exponential backoff.
31+
/// </summary>
32+
public const double DefaultBackoffMultiplier = 2;
33+
34+
/// <summary>
35+
/// The default service configuration that includes the method configuration.
36+
/// </summary>
37+
/// <remarks>
38+
/// This can be applied to a gRPC channel to enable automatic retries for all methods.
39+
/// </remarks>
40+
public static readonly ServiceConfig DefaultServiceConfig;
41+
42+
43+
/// <summary>
44+
/// The default retry policy for gRPC operations.
45+
/// </summary>
46+
/// <remarks>
47+
/// - Retries only for Unavailable status codes (typically connection issues).
48+
/// </remarks>
49+
static readonly Grpc.Net.Client.Configuration.RetryPolicy Default;
50+
51+
/// <summary>
52+
/// The default method configuration that applies the retry policy to all methods.
53+
/// </summary>
54+
/// <remarks>
55+
/// Uses MethodName.Default to apply the retry policy to all gRPC methods.
56+
/// </remarks>
57+
static readonly MethodConfig DefaultMethodConfig;
58+
59+
static GrpcRetryPolicyDefaults()
60+
{
61+
Default = new Grpc.Net.Client.Configuration.RetryPolicy
62+
{
63+
MaxAttempts = DefaultMaxAttempts,
64+
InitialBackoff = TimeSpan.FromMilliseconds(DefaultInitialBackoffMs),
65+
MaxBackoff = TimeSpan.FromMilliseconds(DefaultMaxBackoffMs),
66+
BackoffMultiplier = DefaultBackoffMultiplier,
67+
RetryableStatusCodes = { StatusCode.Unavailable },
68+
};
69+
70+
DefaultMethodConfig = new MethodConfig
71+
{
72+
Names = { MethodName.Default },
73+
RetryPolicy = Default,
74+
};
75+
76+
DefaultServiceConfig = new ServiceConfig
77+
{
78+
MethodConfigs = { DefaultMethodConfig },
79+
};
80+
}
81+
}

src/Worker/AzureManaged/DurableTaskSchedulerWorkerOptions.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using Azure.Identity;
77
using Grpc.Core;
88
using Grpc.Net.Client;
9+
using Grpc.Net.Client.Configuration;
910

1011
namespace Microsoft.DurableTask;
1112

@@ -119,6 +120,7 @@ this.Credential is not null
119120
{
120121
Credentials = ChannelCredentials.Create(channelCreds, managedBackendCreds),
121122
UnsafeUseInsecureChannelCallCredentials = this.AllowInsecureCredentials,
123+
ServiceConfig = GrpcRetryPolicyDefaults.DefaultServiceConfig,
122124
});
123125
}
124126

@@ -134,7 +136,7 @@ this.Credential is not null
134136
case "managedidentity":
135137
return new ManagedIdentityCredential(connectionString.ClientId);
136138
case "workloadidentity":
137-
var opts = new WorkloadIdentityCredentialOptions();
139+
WorkloadIdentityCredentialOptions opts = new WorkloadIdentityCredentialOptions();
138140
if (!string.IsNullOrEmpty(connectionString.ClientId))
139141
{
140142
opts.ClientId = connectionString.ClientId;
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using FluentAssertions;
5+
using Grpc.Core;
6+
using Grpc.Net.Client.Configuration;
7+
using Xunit;
8+
9+
namespace Microsoft.DurableTask.Tests;
10+
11+
public class GrpcRetryPolicyDefaultsTests
12+
{
13+
[Fact]
14+
public void DefaultServiceConfig_ShouldHaveExpectedConfiguration()
15+
{
16+
ServiceConfig serviceConfig = GrpcRetryPolicyDefaults.DefaultServiceConfig;
17+
18+
serviceConfig.Should().NotBeNull();
19+
serviceConfig.MethodConfigs.Should().HaveCount(1);
20+
21+
MethodConfig methodConfig = serviceConfig.MethodConfigs[0];
22+
methodConfig.Names.Should().Contain(MethodName.Default);
23+
methodConfig.RetryPolicy.Should().NotBeNull();
24+
25+
RetryPolicy? retryPolicy = methodConfig.RetryPolicy;
26+
retryPolicy.Should().NotBeNull();
27+
_ = retryPolicy!.MaxAttempts.Should().Be(GrpcRetryPolicyDefaults.DefaultMaxAttempts);
28+
retryPolicy.InitialBackoff.Should().Be(TimeSpan.FromMilliseconds(GrpcRetryPolicyDefaults.DefaultInitialBackoffMs));
29+
retryPolicy.MaxBackoff.Should().Be(TimeSpan.FromMilliseconds(GrpcRetryPolicyDefaults.DefaultMaxBackoffMs));
30+
retryPolicy.BackoffMultiplier.Should().Be(GrpcRetryPolicyDefaults.DefaultBackoffMultiplier);
31+
retryPolicy.RetryableStatusCodes.Should().ContainSingle()
32+
.Which.Should().Be(StatusCode.Unavailable);
33+
}
34+
}

test/Shared/AzureManaged.Tests/Shared.AzureManaged.Tests.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
<ItemGroup>
1313
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
1414
<PackageReference Include="Azure.Identity" />
15+
<PackageReference Include="Grpc.Core" />
16+
<PackageReference Include="Grpc.Net.Client" />
1517
</ItemGroup>
1618

1719
<ItemGroup>

0 commit comments

Comments
 (0)