-
Notifications
You must be signed in to change notification settings - Fork 5k
Make HttpClientFactory implement IDisposable to clean up resources #115826
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Co-authored-by: CarnaViire <3184057+CarnaViire@users.noreply.github.com>
Co-authored-by: CarnaViire <3184057+CarnaViire@users.noreply.github.com>
Tagging subscribers to this area: @dotnet/ncl |
src/libraries/Microsoft.Extensions.Http/src/DefaultHttpClientFactory.cs
Outdated
Show resolved
Hide resolved
…actory.cs Co-authored-by: CarnaViire <3184057+CarnaViire@users.noreply.github.com>
src/libraries/Microsoft.Extensions.Http/src/DefaultHttpClientFactory.cs
Outdated
Show resolved
Hide resolved
src/libraries/Microsoft.Extensions.Http/src/DefaultHttpClientFactory.cs
Outdated
Show resolved
Hide resolved
src/libraries/Microsoft.Extensions.Http/src/DefaultHttpClientFactory.cs
Outdated
Show resolved
Hide resolved
src/libraries/Microsoft.Extensions.Http/src/DefaultHttpClientFactory.cs
Outdated
Show resolved
Hide resolved
src/libraries/Microsoft.Extensions.Http/src/DefaultHttpClientFactory.cs
Outdated
Show resolved
Hide resolved
...osoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/HttpClientFactoryDisposeTests.cs
Outdated
Show resolved
Hide resolved
...osoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/HttpClientFactoryDisposeTests.cs
Outdated
Show resolved
Hide resolved
...osoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/HttpClientFactoryDisposeTests.cs
Outdated
Show resolved
Hide resolved
...osoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/HttpClientFactoryDisposeTests.cs
Outdated
Show resolved
Hide resolved
...osoft.Extensions.Http/tests/Microsoft.Extensions.Http.Tests/HttpClientFactoryDisposeTests.cs
Outdated
Show resolved
Hide resolved
Co-authored-by: CarnaViire <3184057+CarnaViire@users.noreply.github.com>
src/libraries/Microsoft.Extensions.Http/src/ActiveHandlerTrackingEntry.cs
Outdated
Show resolved
Hide resolved
Co-authored-by: CarnaViire <3184057+CarnaViire@users.noreply.github.com>
src/libraries/Microsoft.Extensions.Http/src/ActiveHandlerTrackingEntry.cs
Outdated
Show resolved
Hide resolved
@copilot let's try again |
…logic Co-authored-by: CarnaViire <3184057+CarnaViire@users.noreply.github.com>
I've addressed all the code review feedback in commit 9c60237:
The code builds successfully and is ready for further review. |
src/libraries/Microsoft.Extensions.Http/src/ExpiredHandlerTrackingEntry.cs
Outdated
Show resolved
Hide resolved
…dler is still alive Co-authored-by: CarnaViire <3184057+CarnaViire@users.noreply.github.com>
This comment was marked as resolved.
This comment was marked as resolved.
Co-authored-by: CarnaViire <3184057+CarnaViire@users.noreply.github.com>
... Fixed in commit b0216e5. The test was failing because all clients were being created with the same name "test-client", which causes the factory to reuse the same handler for all clients. I've updated the test to create clients with unique names, ensuring we get 5 distinct handlers that are properly tracked and disposed. |
…logic Co-authored-by: CarnaViire <3184057+CarnaViire@users.noreply.github.com>
Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details. Warning Firewall rules blocked me from connecting to one or more addressesI tried to connect to the following addresses, but was blocked by firewall rules:If you need me to access, download, or install something from one of these locations, you can either:
|
private const int ClientCount = 5; | ||
|
||
[Fact] | ||
public async Task DisposingServiceProvider_DisposesHttpClientFactory_ReleasesResources() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Let's test disposing of the scope as well. This can be done by making DisposeTrackingHandler depend on some scoped service, that is also IDisposable; and then track/count these services in a similar way.
- We lost testing the expired handlers at some point, too. You should use the test factory modification from
Lines 536 to 603 in 4fea27c
private class TestHttpClientFactory : DefaultHttpClientFactory { public TestHttpClientFactory( IServiceProvider services, IServiceScopeFactory scopeFactory, IOptionsMonitor<HttpClientFactoryOptions> optionsMonitor, IEnumerable<IHttpMessageHandlerBuilderFilter> filters) : base(services, scopeFactory, optionsMonitor, filters) { ActiveEntryState = new Dictionary<ActiveHandlerTrackingEntry, (TaskCompletionSource<ActiveHandlerTrackingEntry>, Task)>(); CleanupTimerStarted = new ManualResetEventSlim(initialState: false); } public bool EnableExpiryTimer { get; set; } public bool EnableCleanupTimer { get; set; } public ManualResetEventSlim CleanupTimerStarted { get; } public Dictionary<ActiveHandlerTrackingEntry, (TaskCompletionSource<ActiveHandlerTrackingEntry>, Task)> ActiveEntryState { get; } internal override void StartHandlerEntryTimer(ActiveHandlerTrackingEntry entry) { if (EnableExpiryTimer) { lock (ActiveEntryState) { if (ActiveEntryState.ContainsKey(entry)) { // Timer already started. return; } // Rather than using the actual timer on the actual entry, let's fake it with async. var completionSource = new TaskCompletionSource<ActiveHandlerTrackingEntry>(); var expiryTask = completionSource.Task.ContinueWith(t => { var e = t.Result; ExpiryTimer_Tick(e); lock (ActiveEntryState) { ActiveEntryState.Remove(e); } }); ActiveEntryState.Add(entry, (completionSource, expiryTask)); } } } internal override void StartCleanupTimer() { if (EnableCleanupTimer) { CleanupTimerStarted.Set(); } } internal override void StopCleanupTimer() { if (EnableCleanupTimer) { Assert.True(CleanupTimerStarted.IsSet, "Cleanup timer started"); CleanupTimerStarted.Reset(); } } }
- then you create ClientCount clients,
- then you iterate over test factory's ActiveEntryState -- key is active entry, value is (completionSource, expiryTask) -- and for each kvp you (1) set completionSource's result to the key (active entry), and (2) await expiryTask
- after that all the handlers became expired;
- then you create ClientCount clients again (it could be the same set of names as before -- as all of the initial handlers are now expired, new should be created)
- after that ClientCount handlers are expired, and ClientCount handlers are new and active
Issue Description
When creating and disposing service providers that use HttpClientFactory, there's a memory leak because timers aren't properly cleaned up. This particularly affects scenarios where service providers are frequently created and disposed, such as in test environments.
The problem occurs because:
Changes Made
DefaultHttpClientFactory
implementIDisposable
interfaceStopTimer()
method toActiveHandlerTrackingEntry
to stop and dispose timersDispose()
method inDefaultHttpClientFactory
that:Benefits
Fixes #113494.
Warning
Firewall rules blocked me from connecting to one or more addresses
I tried to connect to the following addresses, but was blocked by firewall rules:
pkgs.dev.azure.com
/home/REDACTED/work/runtime/runtime/.dotnet/dotnet msbuild /m /nologo /clp:Summary /v:minimal /nr:true /warnaserror /p:TreatWarningsAsErrors=true /p:ContinuousIntegrationBuild=false /home/REDACTED/work/runtime/runtime/artifacts/toolset/restore.proj /t:__WriteToolsetLocation /clp:ErrorsOnly;NoSummary /p:__ToolsetLocationOutputFile=/home/REDACTED/work/runtime/runtime/artifacts/toolset/10.0.0-beta.25260.104.txt
(dns block)./.dotnet/dotnet build src/libraries/Microsoft.Extensions.Http/src/Microsoft.Extensions.Http.csproj
(dns block)If you need me to access, download, or install something from one of these locations, you can either:
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.