Skip to content

Commit 8f6c61d

Browse files
thomhurstclaude
andauthored
fix: prevent memory leaks in Command and Http classes (#1677)
- Command: Properly dispose CancellationTokenRegistration using 'await using' to ensure the registration callback is unregistered when the method exits (fixes #1632) - Http: Remove incorrect disposal of HttpClient from IHttpClientFactory. The factory manages HttpClient lifetime, so disposing them can cause issues. Now properly tracks and disposes ServiceProviders for internally created HttpClients (fixes #1567) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent ce7e9c6 commit 8f6c61d

2 files changed

Lines changed: 12 additions & 7 deletions

File tree

src/ModularPipelines/Context/Command.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ private async Task<CommandResult> Of(CliWrap.Command command,
270270

271271
using var forcefulCancellationToken = new CancellationTokenSource();
272272

273-
cancellationToken.Register(() =>
273+
await using var registration = cancellationToken.Register(() =>
274274
{
275275
try
276276
{

src/ModularPipelines/Http/Http.cs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ internal class Http : IHttp, IDisposable
1616
private readonly IHttpLogger _httpLogger;
1717
private readonly IOptions<PipelineOptions> _pipelineOptions;
1818

19-
private readonly ConcurrentDictionary<HttpLoggingType, HttpClient> _loggingHttpClients = new();
19+
private readonly ConcurrentDictionary<HttpLoggingType, (HttpClient Client, ServiceProvider Provider)> _loggingHttpClients = new();
2020

2121
public Http(HttpClient defaultHttpClient,
2222
IModuleLoggerProvider moduleLoggerProvider,
@@ -94,19 +94,24 @@ public HttpClient GetLoggingHttpClient(HttpLoggingType loggingType)
9494
httpClientBuilder.AddHttpMessageHandler<StatusCodeLoggingHttpHandler>();
9595
}
9696

97-
return serviceCollection.BuildServiceProvider()
97+
var serviceProvider = serviceCollection.BuildServiceProvider();
98+
var httpClient = serviceProvider
9899
.GetRequiredService<ModularPipelinesHttpClientProvider>()
99100
.HttpClient;
100-
});
101+
102+
return (httpClient, serviceProvider);
103+
}).Client;
101104
}
102105

103106
public void Dispose()
104107
{
105-
HttpClient.Dispose();
108+
// Note: HttpClient is obtained from IHttpClientFactory via DI and should NOT be disposed.
109+
// The factory manages the lifetime of HttpClient instances.
106110

107-
foreach (var httpClient in _loggingHttpClients.Values)
111+
// Dispose the ServiceProviders which will clean up their internal resources
112+
foreach (var (_, provider) in _loggingHttpClients.Values)
108113
{
109-
httpClient.Dispose();
114+
provider.Dispose();
110115
}
111116
}
112117

0 commit comments

Comments
 (0)