Skip to content

Commit 7b02a40

Browse files
authored
Removed HttpClient timeout (#24)
* Building the HttpClient used for gRPC connections from IHttpClientFactory, if available. Removes 100 second default timeout from HttpClient. Minor refactoring to use switch expressions and method groups. * Package updates * Explicitly marked IHttpClientFactory as optional --------- Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
1 parent 4b8ff50 commit 7b02a40

File tree

5 files changed

+121
-114
lines changed

5 files changed

+121
-114
lines changed

Directory.Packages.props

Lines changed: 29 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -6,58 +6,53 @@
66
-->
77
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
88
</PropertyGroup>
9-
109
<!-- Microsoft.Extensions.* Packages -->
1110
<ItemGroup>
12-
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.5" />
13-
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.5" />
14-
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.5" />
15-
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.5" />
16-
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.5" />
17-
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.5" />
18-
<PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.5" />
19-
<PackageVersion Include="Microsoft.Extensions.Options.DataAnnotations" Version="9.0.5" />
11+
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.8" />
12+
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.8" />
13+
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.8" />
14+
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.8" />
15+
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.8" />
16+
<PackageVersion Include="Microsoft.Extensions.Http" Version="9.0.8" />
17+
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.8" />
18+
<PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.8" />
19+
<PackageVersion Include="Microsoft.Extensions.Options.DataAnnotations" Version="9.0.8" />
2020
</ItemGroup>
21-
2221
<!-- Azure.* Packages -->
2322
<ItemGroup>
24-
<PackageVersion Include="Azure.Identity" Version="1.13.1" />
25-
<PackageVersion Include="Microsoft.Azure.Functions.Worker" Version="1.21.0" />
23+
<PackageVersion Include="Azure.Identity" Version="1.15.0" />
24+
<PackageVersion Include="Microsoft.Azure.Functions.Worker" Version="1.24.0" />
2625
</ItemGroup>
27-
2826
<!-- DurableTask Packages -->
2927
<ItemGroup>
3028
<PackageVersion Include="Microsoft.Azure.DurableTask.Core" Version="3.1.0" />
31-
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask" Version="1.2.2" />
29+
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask" Version="1.7.1" />
3230
</ItemGroup>
33-
3431
<!-- Grpc / Protobuf Packages -->
3532
<ItemGroup>
36-
<PackageVersion Include="Google.Protobuf" Version="3.31.0" />
33+
<PackageVersion Include="Google.Protobuf" Version="3.32.0" />
3734
<PackageVersion Include="Grpc.Core" Version="2.46.6" />
3835
<PackageVersion Include="Grpc.Net.Client" Version="2.71.0" />
3936
<PackageVersion Include="Grpc.Tools" Version="2.72.0" />
4037
<PackageVersion Include="Grpc.AspNetCore.Server" Version="2.71.0" />
4138
</ItemGroup>
42-
4339
<!-- Microsoft.CodeAnalysis.* Packages -->
4440
<ItemGroup>
45-
<PackageVersion Include="Microsoft.CodeAnalysis" Version="4.9.2" />
46-
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
47-
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
48-
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.9.2" />
49-
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.9.2" />
41+
<PackageVersion Include="Microsoft.CodeAnalysis" Version="4.14.0" />
42+
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="4.14.0" />
43+
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
44+
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.14.0" />
45+
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.14.0" />
5046
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing" Version="1.1.2" />
5147
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing" Version="1.1.2" />
5248
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing" Version="1.1.2" />
53-
<PackageVersion Include="Microsoft.CodeAnalysis.ResxSourceGenerator" Version="3.11.0-beta1.24165.2" />
49+
<PackageVersion Include="Microsoft.CodeAnalysis.ResxSourceGenerator" Version="3.12.0-beta1.25218.8" />
5450
</ItemGroup>
55-
5651
<!-- Testing Frameworks & Analysis Packages -->
5752
<ItemGroup>
58-
<PackageVersion Include="BenchmarkDotNet" Version="0.14.0" />
53+
<PackageVersion Include="BenchmarkDotNet" Version="0.15.2" />
5954
<PackageVersion Include="FluentAssertions" Version="6.7.0" />
60-
<PackageVersion Include="FluentAssertions.Analyzers" Version="0.17.2"/>
55+
<PackageVersion Include="FluentAssertions.Analyzers" Version="0.17.2" />
6156
<PackageVersion Include="GitHubActionsTestLogger" Version="2.4.1" />
6257
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
6358
<PackageVersion Include="MinVer" Version="6.0.0" />
@@ -67,18 +62,16 @@
6762
<PackageVersion Include="xunit" Version="2.9.3" />
6863
<PackageVersion Include="xunit.extensibility.core" Version="2.9.3" />
6964
<PackageVersion Include="xunit.abstractions" Version="2.0.3" />
70-
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.0"/>
65+
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
7166
<PackageVersion Include="Xunit.Combinatorial" Version="1.6.24" />
7267
</ItemGroup>
73-
7468
<!-- Base-class library dependencies -->
7569
<ItemGroup>
76-
<PackageVersion Include="DotNext" Version="4.13.1" />
77-
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="1.1.1" />
78-
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="6.0.0" />
79-
<PackageVersion Include="System.Linq.Async" Version="6.0.1" />
80-
<PackageVersion Include="System.Text.Json" Version="9.0.5" />
81-
<PackageVersion Include="System.Collections.Immutable" Version="9.0.5" />
70+
<PackageVersion Include="DotNext" Version="5.24.0" />
71+
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
72+
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.8" />
73+
<PackageVersion Include="System.Linq.Async" Version="6.0.3" />
74+
<PackageVersion Include="System.Text.Json" Version="9.0.8" />
75+
<PackageVersion Include="System.Collections.Immutable" Version="9.0.8" />
8276
</ItemGroup>
83-
84-
</Project>
77+
</Project>

src/Worker/Core/DependencyInjection/ServiceCollectionExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ static void ConditionalConfigureBuilder(
7272
// The added toggle logic is because we cannot use TryAddEnumerable logic as
7373
// we would have to dynamically compile a lambda to have it work correctly.
7474
ConfigureDurableOptions(services, builder.Name);
75-
services.AddSingleton(sp => builder.Build(sp));
75+
services.AddSingleton(builder.Build);
7676
}
7777

7878
static IServiceCollection ConfigureDurableOptions(IServiceCollection services, string name)

src/Worker/Grpc/GrpcDurableTaskWorker.Processor.cs

Lines changed: 51 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22
// Licensed under the MIT License.
33

44
using System.Text;
5+
using Dapr.DurableTask.Abstractions;
6+
using Dapr.DurableTask.Entities;
7+
using Dapr.DurableTask.Worker.Shims;
58
using DurableTask.Core;
69
using DurableTask.Core.Entities;
710
using DurableTask.Core.Entities.OperationFormat;
811
using DurableTask.Core.History;
9-
using Dapr.DurableTask.Abstractions;
10-
using Dapr.DurableTask.Entities;
11-
using Dapr.DurableTask.Worker.Shims;
1212
using Microsoft.Extensions.DependencyInjection;
1313
using Microsoft.Extensions.Logging;
1414
using static Dapr.DurableTask.Protobuf.TaskHubSidecarService;
@@ -101,21 +101,14 @@ public async Task ExecuteAsync(CancellationToken cancellation)
101101

102102
static string GetActionsListForLogging(IReadOnlyList<P.OrchestratorAction> actions)
103103
{
104-
if (actions.Count == 0)
105-
{
106-
return string.Empty;
107-
}
108-
else if (actions.Count == 1)
104+
return actions.Count switch
109105
{
110-
return actions[0].OrchestratorActionTypeCase.ToString();
111-
}
112-
else
113-
{
114-
// Returns something like "ScheduleTask x5, CreateTimer x1,..."
115-
return string.Join(", ", actions
116-
.GroupBy(a => a.OrchestratorActionTypeCase)
117-
.Select(group => $"{group.Key} x{group.Count()}"));
118-
}
106+
0 => string.Empty,
107+
1 => actions[0].OrchestratorActionTypeCase.ToString(),
108+
_ => string.Join(
109+
", ",
110+
actions.GroupBy(a => a.OrchestratorActionTypeCase).Select(group => $"{group.Key} x{group.Count()}")),
111+
};
119112
}
120113

121114
static P.TaskFailureDetails? EvaluateOrchestrationVersioning(DurableTaskWorkerOptions.VersioningOptions? versioning, string orchestrationVersion, out bool versionCheckFailed)
@@ -253,51 +246,48 @@ async Task ProcessWorkItemsAsync(AsyncServerStreamingCall<P.WorkItem> stream, Ca
253246
{
254247
await foreach (P.WorkItem workItem in stream.ResponseStream.ReadAllAsync(cancellationToken: cancellation))
255248
{
256-
if (workItem.RequestCase == P.WorkItem.RequestOneofCase.OrchestratorRequest)
257-
{
258-
this.RunBackgroundTask(
259-
workItem,
260-
() => this.OnRunOrchestratorAsync(
261-
workItem.OrchestratorRequest,
262-
workItem.CompletionToken,
263-
cancellation));
264-
}
265-
else if (workItem.RequestCase == P.WorkItem.RequestOneofCase.ActivityRequest)
249+
switch (workItem.RequestCase)
266250
{
267-
this.RunBackgroundTask(
268-
workItem,
269-
() => this.OnRunActivityAsync(
270-
workItem.ActivityRequest,
271-
workItem.CompletionToken,
272-
cancellation));
273-
}
274-
else if (workItem.RequestCase == P.WorkItem.RequestOneofCase.EntityRequest)
275-
{
276-
this.RunBackgroundTask(
277-
workItem,
278-
() => this.OnRunEntityBatchAsync(workItem.EntityRequest.ToEntityBatchRequest(), cancellation));
279-
}
280-
else if (workItem.RequestCase == P.WorkItem.RequestOneofCase.EntityRequestV2)
281-
{
282-
workItem.EntityRequestV2.ToEntityBatchRequest(
283-
out EntityBatchRequest batchRequest,
284-
out List<P.OperationInfo> operationInfos);
285-
286-
this.RunBackgroundTask(
287-
workItem,
288-
() => this.OnRunEntityBatchAsync(
289-
batchRequest,
290-
cancellation,
291-
workItem.CompletionToken,
292-
operationInfos));
293-
}
294-
else if (workItem.RequestCase == P.WorkItem.RequestOneofCase.HealthPing)
295-
{
296-
// No-op
297-
}
298-
else
299-
{
300-
this.Logger.UnexpectedWorkItemType(workItem.RequestCase.ToString());
251+
case P.WorkItem.RequestOneofCase.OrchestratorRequest:
252+
this.RunBackgroundTask(
253+
workItem,
254+
() => this.OnRunOrchestratorAsync(
255+
workItem.OrchestratorRequest,
256+
workItem.CompletionToken,
257+
cancellation));
258+
break;
259+
case P.WorkItem.RequestOneofCase.ActivityRequest:
260+
this.RunBackgroundTask(
261+
workItem,
262+
() => this.OnRunActivityAsync(
263+
workItem.ActivityRequest,
264+
workItem.CompletionToken,
265+
cancellation));
266+
break;
267+
case P.WorkItem.RequestOneofCase.EntityRequest:
268+
this.RunBackgroundTask(
269+
workItem,
270+
() => this.OnRunEntityBatchAsync(workItem.EntityRequest.ToEntityBatchRequest(), cancellation));
271+
break;
272+
case P.WorkItem.RequestOneofCase.EntityRequestV2:
273+
workItem.EntityRequestV2.ToEntityBatchRequest(
274+
out EntityBatchRequest batchRequest,
275+
out List<P.OperationInfo> operationInfos);
276+
277+
this.RunBackgroundTask(
278+
workItem,
279+
() => this.OnRunEntityBatchAsync(
280+
batchRequest,
281+
cancellation,
282+
workItem.CompletionToken,
283+
operationInfos));
284+
break;
285+
case P.WorkItem.RequestOneofCase.HealthPing:
286+
// No-op
287+
break;
288+
default:
289+
this.Logger.UnexpectedWorkItemType(workItem.RequestCase.ToString());
290+
break;
301291
}
302292
}
303293
}

src/Worker/Grpc/GrpcDurableTaskWorker.cs

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ sealed partial class GrpcDurableTaskWorker : DurableTaskWorker
1515
readonly GrpcDurableTaskWorkerOptions grpcOptions;
1616
readonly DurableTaskWorkerOptions workerOptions;
1717
readonly IServiceProvider services;
18+
readonly IHttpClientFactory? httpClientFactory;
1819
readonly ILoggerFactory loggerFactory;
1920
readonly ILogger logger;
2021

@@ -23,6 +24,7 @@ sealed partial class GrpcDurableTaskWorker : DurableTaskWorker
2324
/// </summary>
2425
/// <param name="name">The name of the worker.</param>
2526
/// <param name="factory">The task factory.</param>
27+
/// <param name="httpClientFactory">The HTTP client factory.</param>
2628
/// <param name="grpcOptions">The gRPC-specific worker options.</param>
2729
/// <param name="workerOptions">The generic worker options.</param>
2830
/// <param name="services">The service provider.</param>
@@ -33,12 +35,14 @@ public GrpcDurableTaskWorker(
3335
IOptionsMonitor<GrpcDurableTaskWorkerOptions> grpcOptions,
3436
IOptionsMonitor<DurableTaskWorkerOptions> workerOptions,
3537
IServiceProvider services,
36-
ILoggerFactory loggerFactory)
38+
ILoggerFactory loggerFactory,
39+
IHttpClientFactory? httpClientFactory = null)
3740
: base(name, factory)
3841
{
3942
this.grpcOptions = Check.NotNull(grpcOptions).Get(name);
4043
this.workerOptions = Check.NotNull(workerOptions).Get(name);
4144
this.services = Check.NotNull(services);
45+
this.httpClientFactory = httpClientFactory;
4246
this.loggerFactory = Check.NotNull(loggerFactory);
4347
this.logger = loggerFactory.CreateLogger("Dapr.DurableTask");
4448
}
@@ -51,29 +55,45 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
5155
await new Processor(this, new(callInvoker)).ExecuteAsync(stoppingToken);
5256
}
5357

54-
#if NET6_0_OR_GREATER
55-
static GrpcChannel GetChannel(string? address)
58+
GrpcChannel GetChannel(string? address)
5659
{
57-
if (string.IsNullOrEmpty(address))
60+
if (string.IsNullOrWhiteSpace(address))
5861
{
5962
address = "http://localhost:4001";
6063
}
6164

62-
return GrpcChannel.ForAddress(address);
63-
}
64-
#endif
65+
// Create the HttpClient so we can avoid the default 100 second timeout
66+
// As this service is created as a singleton, it's ok to create the HttpClient once here as well
67+
// Create the client from IHttpClientFactory if available, otherwise create a new instance
68+
var httpClient = this.httpClientFactory?.CreateClient() ?? new HttpClient();
69+
httpClient.Timeout = Timeout.InfiniteTimeSpan;
6570

66-
#if NETSTANDARD2_0
67-
static GrpcChannel GetChannel(string? address)
68-
{
69-
if (string.IsNullOrEmpty(address))
71+
// Configure keep-alive settings to maintain long-lived connections
72+
var handler = new SocketsHttpHandler
7073
{
71-
address = "localhost:4001";
72-
}
74+
// Enable keep-alive
75+
KeepAlivePingPolicy = HttpKeepAlivePingPolicy.Always,
76+
KeepAlivePingDelay = TimeSpan.FromSeconds(30),
77+
KeepAlivePingTimeout = TimeSpan.FromSeconds(30),
78+
79+
// Pooled connections are reused and won't time out from inactivity
80+
EnableMultipleHttp2Connections = true,
7381

74-
return new(address, ChannelCredentials.Insecure);
82+
// Set a very long connection lifetime - this allows a controlled connection refresh strategy
83+
PooledConnectionLifetime = TimeSpan.FromDays(1),
84+
85+
// Disable idle timeout entirely
86+
PooledConnectionIdleTimeout = Timeout.InfiniteTimeSpan,
87+
};
88+
89+
return GrpcChannel.ForAddress(address, new GrpcChannelOptions
90+
{
91+
HttpHandler = handler,
92+
HttpClient = httpClient,
93+
MaxReceiveMessageSize = null, // No message size limit
94+
DisposeHttpClient = false, // Lifetime managed by the HttpClientFactory
95+
});
7596
}
76-
#endif
7797

7898
AsyncDisposable GetCallInvoker(out CallInvoker callInvoker, out string address)
7999
{
@@ -91,7 +111,7 @@ AsyncDisposable GetCallInvoker(out CallInvoker callInvoker, out string address)
91111
return default;
92112
}
93113

94-
c = GetChannel(this.grpcOptions.Address);
114+
c = this.GetChannel(this.grpcOptions.Address);
95115
callInvoker = c.CreateCallInvoker();
96116
address = c.Target;
97117
return new AsyncDisposable(() => new(c.ShutdownAsync()));

src/Worker/Grpc/Worker.Grpc.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,8 @@
1818
<SharedSection Include="DependencyInjection" />
1919
<SharedSection Include="Grpc" />
2020
</ItemGroup>
21+
22+
<ItemGroup>
23+
<PackageReference Include="Microsoft.Extensions.Http" />
24+
</ItemGroup>
2125
</Project>

0 commit comments

Comments
 (0)