Skip to content

Warning about incorrect lifecycle policy #2315

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

Closed
wants to merge 3 commits into from

Conversation

jzabroski
Copy link

Summary

In several places I've worked, people use HttpClient incorrectly. While some of this can be chalked up to failure to "Read the ... Documentation", I also think the documentation could make use of WARNING Note Blocks to call-out the best way to use HttpClient.

Even code bases I've looked at (using Reflector) written by quality software vendors frequently have many HttpClient objects created. See also this Reddit thread today where someone was hit by the same issue: https://www.reddit.com/r/csharp/comments/bdemhl/why_have_we_been_programmed_to_dispose_of_things/

While I can appreciate developers should read the whole remarks, this is probably the most frequent error I encounter with people using HttpClient. As such, I think it should be prominently displayed.

@jzabroski jzabroski requested a review from karelz as a code owner April 16, 2019 14:45
@@ -28,6 +28,10 @@
<format type="text/markdown"><![CDATA[

## Remarks

> [!WARNING]
> <xref:System.Net.Http.HttpClient> is intended to be instantiated once and re-used throughout the life of an application. Instantiating an HttpClient class for every request will exhaust the number of sockets available under heavy loads. This will result in SocketException errors.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @jzabroski. Unfortunately, this recommendation is not complete.
We need to warn users also about having long-existing HttpClient instances as that may lead to stale DNS usage. That can lead to non-functional connections.

The recommendation should be either to:

  • Use HttpClient instances as statics, which are recycled on regular basis, or
  • Use HttpClientFactor which works around this problem, or
  • Use static instances on .NET Core while setting System.Net.Http.SocketsHttpHandler.PooledConnectionLifetime.

@@ -56,7 +60,7 @@

9. <xref:System.Net.Http.HttpClient.SendAsync%2A>

<xref:System.Net.Http.HttpClient> is intended to be instantiated once and re-used throughout the life of an application. Instantiating an HttpClient class for every request will exhaust the number of sockets available under heavy loads. This will result in SocketException errors. Below is an example using HttpClient correctly.
Below is an example using HttpClient correctly:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The below example is technically also incomplete and needs update

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I agree. I also dislike how the Examples section comes before the Remarks, but I figured we should tackle one issue at a time. I see you are a developer rather than a tech writer, so your mindset is probably more of a developer than a tech writer (if I may suggest).

Would you rather we re-work the whole article in one go or do this piece-wise?

Copy link
Member

@karelz karelz Apr 16, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I am developer and have very little tech writer mindset - but I know people who can help with that 😄 - paging @mairaw @rpetrusha

Rather than piece-wise, I would prefer overall re-work to accomodate to all needs. (unless there are reasons against I am not aware of)

BTW: This is one of the key topics on our backlog - it was brought up in MVP session feedback we had in March at MVP Summit. I just didn't get to publishing the full list of feedback points as our detailed plan/roadmap.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would be happy to do the rework. Give me about 1-2 weeks. I'll fit it in as I have free time; would rather underpromise and over-deliver.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks so much. It will be great help!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that sounds like a great link

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@karelz What are your thoughts about adding some examples of using Polly and https://www.nuget.org/packages/Polly.Extensions.Http/ nuget PackageReference to a project. These are "opinionated" libraries to assist in transient fault handling.

Polly project is a member of the .NET Foundation https://github.com/App-vNext/Polly.Extensions.Http
It is basically the modern equivalent of Microsoft Enterprise Library Transient Fault Handling Application Block (last updated in 2013). Enterprise Library logic has about 4M nuget downloads, whereas Polly has 12M.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: Consider adding link to Polly Nuget Package on https://docs.microsoft.com/en-us/dotnet/standard/microservices-architecture/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests#issues-with-the-original-httpclient-class-available-in-net-core as part of this PR. The phrase Polly is used throughout this page without defining what it is. e.g, "resilient HTTP calls by integrating Polly with it."

I also think the "Issues with using HttpClient" section on that page can be tidied up into a list of numbered points and the whole "As a first issue," and "But there’s a second issue" language can be consolidated into markdown numbered points.

It should read like something like this:

  1. HttpClient is intended to be instantiated once and reused throughout the life of an application. Despite HttpClient implementing IDisposable, constantly constructing and disposing of HttpClient instances can cause serious system-wide issues. <insert explanation of socket exhaustion here />
  2. However, a singleton or static HttpClient doesn't intelligently handle transient faults such as DNS changes (which can occur on failover), as explained in this issue at the .NET Core GitHub repo.
    Blah blah please use HttpClientFactory with Polly.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should add links to packages which are not part of the platform itself. Even if they are .NET Foundation projects.
I can be convinced if there is precedent in .NET docs (@mairaw?) - in which case I expect deeper ecosystem scouting should happen first, so that we do not promote just one nuget packge we happen to know about ...

Copy link
Author

@jzabroski jzabroski Apr 26, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@karelz I thought this over, and I think you're right, ".NET Foundation" by itself is not enough to warrant inclusion in any Microsoft Docs project. At minimum, the project should ALSO support strong named assemblies in a sensible manner.

The lack of (sensible) strong named assemblies in Json.NET is exactly what has caused hundreds if not thousands of issues for Microsoft-employed developers and the outstanding community, mostly ASP.NET developer community.

You can read about it here: http://james.newtonking.com/archive/2012/04/04/json-net-strong-naming-and-assembly-version-numbers and JamesNK/Newtonsoft.Json#1001 where you can see that the Azure SDK team was bit by strong naming policy: Azure/azure-sdk-for-net#4380

Anyway, this is just some thoughts to share with you - it's a tangent but the general problem of AutoGenerateBindingRedirects and loading the right assembly has been something I've been on a war path for the last 6+ months.

@rpetrusha rpetrusha added the ✨ 1st-time dotnet-api-docs contributor! Indicates PRs from new contributors to the dotnet-api-docs repository label Apr 16, 2019
@rpetrusha rpetrusha added this to the April 2019 milestone Apr 16, 2019
Copy link

@rpetrusha rpetrusha left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @jzabroski, for contributing to the dotnet/docs repo and making the guidance for the use of HttpClient more prominent. I've suggested that SocketException be made a link, but @karelz has added some more substantial comments for you to consider.

@rpetrusha
Copy link

Thank you so much, @jzabroski. We appreciate your contribution, so take the time you need. When you've made the updates, just add the changes-addressed label so that we know your PR is ready for a new review.

@jzabroski
Copy link
Author

jzabroski commented Apr 22, 2019 via email

@karelz
Copy link
Member

karelz commented Apr 22, 2019

If it is already mentioned from MS docs, then that is different story :) ... is that the case?

@tdykstra
Copy link
Contributor

@jzabroski Do you still plan to implement the recommended changes in this PR?

@tdykstra tdykstra added the waiting-on-feedback Indicates PRs that are waiting for feedback from SMEs before they can be merged label Oct 14, 2019
@jzabroski
Copy link
Author

@tdykstra Yes. I was thinking about exactly this topic this weekend.

@tdykstra tdykstra added doc-enhancement Improve the current content and removed waiting-on-feedback Indicates PRs that are waiting for feedback from SMEs before they can be merged labels Oct 15, 2019
@tdykstra
Copy link
Contributor

tdykstra commented Feb 3, 2020

@jzabroski Do you still plan to implement the recommended changes in this PR?

@tdykstra tdykstra added needs-author-action An issue or pull request that requires more info or actions from the author. and removed doc-enhancement Improve the current content labels Feb 3, 2020
@jzabroski
Copy link
Author

jzabroski commented Feb 3, 2020 via email

@jzabroski
Copy link
Author

jzabroski commented Feb 14, 2020 via email

@jzabroski
Copy link
Author

Items to improve

@carlossanlop
Copy link
Contributor

@jzabroski will you have a chance to continue working on this PR?

@BillWagner BillWagner modified the milestones: March 2020, June 2020 Jun 1, 2020
Copy link
Contributor

@gewarren gewarren left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made some stop-gap improvements. Can we track the remaining work in a separate issue(s) so we can get this merged?

@gewarren
Copy link
Contributor

gewarren commented Sep 3, 2020

I created this issue to track the remaining work: https://github.com/dotnet/dotnet-api-docs/issues/4786

@gewarren
Copy link
Contributor

gewarren commented Sep 3, 2020

@karelz You had requested changes on this PR. Can you re-review it to see if it can be merged in its current state, with the other improvement items tracked by #4786?

@carlossanlop
Copy link
Contributor

Ping @dotnet/ncl - Can someone in the networking team take a look at this PR?

Copy link
Member

@karelz karelz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @jzabroski!
We've learned more about HttpClientFactory and we should reword its part and not recommend it as option when HttpClient is used directly. It is suited only for DI environments. And HttpClient can be created per request when it is created by HttpClientFactory.

Comment on lines +36 to +37
> - Use <xref:System.Net.Http.HttpClientFactoryExtensions.CreateClient(IHttpClientFactory)> to create the <xref:System.Net.Http.HttpClient>.
> - On .NET Core and .NET 5.0 and later, use static instances and set <xref:System.Net.Http.SocketsHttpHandler.PooledConnectionLifetime>.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
> - Use <xref:System.Net.Http.HttpClientFactoryExtensions.CreateClient(IHttpClientFactory)> to create the <xref:System.Net.Http.HttpClient>.
> - On .NET Core and .NET 5.0 and later, use static instances and set <xref:System.Net.Http.SocketsHttpHandler.PooledConnectionLifetime>.
> - On .NET Core 2.1 and later, and .NET 5.0 and later, use static instances and set <xref:System.Net.Http.SocketsHttpHandler.PooledConnectionLifetime>.

@@ -28,6 +28,14 @@
<format type="text/markdown"><![CDATA[

## Remarks

> [!WARNING]
> - <xref:System.Net.Http.HttpClient> is intended to be instantiated once and re-used throughout the life of an application. Instantiating an <xref:System.Net.Http.HttpClient> class for every request will exhaust the number of sockets available under heavy loads and result in a <xref:System.Net.Sockets.SocketException>. However, having long-lived <xref:System.Net.Http.HttpClient> instances can lead to stale DNS usage and non-functional connections. Use one of the following options to avoid these problems:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
> - <xref:System.Net.Http.HttpClient> is intended to be instantiated once and re-used throughout the life of an application. Instantiating an <xref:System.Net.Http.HttpClient> class for every request will exhaust the number of sockets available under heavy loads and result in a <xref:System.Net.Sockets.SocketException>. However, having long-lived <xref:System.Net.Http.HttpClient> instances can lead to stale DNS usage and non-functional connections. Use one of the following options to avoid these problems:
> - <xref:System.Net.Http.HttpClient> is intended to be instantiated once and re-used throughout the life of an application, unless it was created by `HttpClientFactory`. Instantiating an <xref:System.Net.Http.HttpClient> class directly for every request will exhaust the number of sockets available under heavy loads and result in a <xref:System.Net.Sockets.SocketException>. However, having long-lived <xref:System.Net.Http.HttpClient> instances can lead to stale DNS usage and non-functional connections. Use one of the following options to avoid these problems:

@karelz karelz requested a review from CarnaViire February 15, 2021 11:36
@CarnaViire
Copy link
Member

I'm ok with the changes proposed by @karelz, I can approve the PR as soon as the changes are commited.

@jzabroski
Copy link
Author

I'll make them today

Base automatically changed from master to main March 5, 2021 20:52
@gewarren gewarren requested a review from a team as a code owner March 5, 2021 20:52
@@ -28,6 +28,14 @@
<format type="text/markdown"><![CDATA[

## Remarks

> [!WARNING]
> - <xref:System.Net.Http.HttpClient> is intended to be instantiated once and re-used throughout the life of an application. Instantiating an <xref:System.Net.Http.HttpClient> class for every request will exhaust the number of sockets available under heavy loads and result in a <xref:System.Net.Sockets.SocketException>. However, having long-lived <xref:System.Net.Http.HttpClient> instances can lead to stale DNS usage and non-functional connections. Use one of the following options to avoid these problems:
Copy link
Contributor

@geoffkizer geoffkizer Mar 6, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem with saying "instantiated once" is that it makes users think they literally should not create more than one of these, which is incorrect.

An instance of HttpClient basically corresponds to a connection pool. So you want to reuse the same HttpClient instance because it will reuse connections from that pool. But there are cases where you don't care about sharing/reusing connections, e.g. when you are talking to two different origin servers and thus wouldn't be sharing connections anyway. And if you want to have, e.g. different authentication settings or redirect settings or version policy or whatever, it it totally fine and, in fact, advisable to have a different HttpClient instance for each of these.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@karelz @geoffkizer I'm sorry this fell by the wayside. The PR comments coincided with me getting covid last year, and the recovery/brain fog limited my mental capacity for "a good bit", as we say in the south.

We've learned more about HttpClientFactory and we should reword its part and not recommend it as option when HttpClient is used directly. It is suited only for DI environments.
-- @karelz

This actually doesn't make total sense to me. Can you explain why?

An instance of HttpClient basically corresponds to a connection pool. So you want to reuse the same HttpClient instance because it will reuse connections from that pool.
-- @geoffkizer

That's all fine, except that "basically" is vague, too.

IHttpClientBuilder defines an explicit lifetime for re-use, here:

https://github.com/dotnet/runtime/blob/215b39abf947da7a40b0cb137eab4bceb24ad3e3/src/libraries/Microsoft.Extensions.Http/src/DependencyInjection/HttpClientBuilderExtensions.cs#L504-L518

IHttpClientBuilder also mentions that the HttpMessageHandler is what gets pooled, so perhaps you mean to say "HttpClient provides a connection pool for HttpMessageHandler instances."

Here is another area that likely need improving:

ITypedHttpClientFactory confusingly doesn't use DI but relies on a static cache - and thus should not be confused with IHttpClientFactory

https://github.com/dotnet/runtime/blob/57bfe474518ab5b7cfe6bf7424a79ce3af9d6657/src/libraries/Microsoft.Extensions.Http/src/ITypedHttpClientFactory.cs#L31-L32

This is a really bizarre remark to me. Just reading it sounded incorrect. It reads to the cautionary reader that there is a lifetime scope mismatch, when you think about it!

So, I decided to dig deeper and found THIS which confirmed my worry - it's a cache that has no fault tolerance mechanisms and is basically a memory leak:

https://github.com/dotnet/runtime/blob/46871b8541fbc2d2f3ff27207597b3a38792a010/src/libraries/Microsoft.Extensions.Http/src/DefaultTypedHttpClientFactory.cs#L33-L36

ITypedHttpClientFactory has a reference to an undefined variable.

https://github.com/dotnet/runtime/blob/57bfe474518ab5b7cfe6bf7424a79ce3af9d6657/src/libraries/Microsoft.Extensions.Http/src/ITypedHttpClientFactory.cs#L71

_exampleClient is undefined

HttpClientFactoryExtensions - IHttpClientFactory has a "default configuration" 👀 ?

This also doesn't read well - how can an interface have a "default configuration" 👀 ?

https://github.com/dotnet/runtime/blob/215b39abf947da7a40b0cb137eab4bceb24ad3e3/src/libraries/Microsoft.Extensions.Http/src/HttpClientFactoryExtensions.cs#L14

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've learned more about HttpClientFactory and we should reword its part and not recommend it as option when HttpClient is used directly. It is suited only for DI environments.

This actually doesn't make total sense to me. Can you explain why?

Not sure what is not clear. It is statement. HttpClientFactory is designed for DI system. It is cumbersome to use it outside of DI environment.

Copy link
Author

@jzabroski jzabroski May 30, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me rephrase:

If the article Use IHttpClientFactory to implement resilient HTTP requests is correct guidance, and not using HttpClientFactory leads to non-resilient HTTP Requests, why would DI vs. non-DI be the controlling factor for its use?

I think saying it is only suited for DI environments is rather confusing in light of the problems highlighted with HttpClient. But perhaps HttpClient has been refactored in newer versions of .NET 5/6 and this no longer applies? See specifically this part of the article which addresses HttpClient shortcomings: https://docs.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests#issues-with-the-original-httpclient-class-available-in-net

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I think I see the problem, further down in that same article I linked, there is a caution about HttpClientFactory being somewhat tightly coupled to Microsoft.Extensions.DependencyInjection nuget package, with a GitHub discussion linked for more info: dotnet/aspnetcore#28385

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, if you scroll down to the bottom of the aforementioned GitHub discussion, you will see a comment by an end user that is fairly lucid (unfortunately, Eric Sampson then decided to close the thread because I guess it was too flamboyant): dotnet/aspnetcore#28385 (comment)

In particular, "Example 4: You're creating a redistributable web-service client library class:" points out that "Figuring out how to handle HttpClient when you don't own the entrypoint or host program that will run your code is very difficult."

Separtely, Eric's reply - go look at the Azure SDK for .NET - is not good. Not only was there no direct link to the code, but Guess what, I followed his suggestion and found production code commented out in ServiceClient.cs - how is this the example Microsoft wants us to follow? :)

https://github.com/Azure/azure-sdk-for-net/blob/4162f6fa2445b2127468b9cfd080f01c9da88eba/sdk/mgmtcommon/ClientRuntime/ClientRuntime/ServiceClient.cs#L33

https://github.com/Azure/azure-sdk-for-net/blob/4162f6fa2445b2127468b9cfd080f01c9da88eba/sdk/mgmtcommon/ClientRuntime/ClientRuntime/ServiceClient.cs#L89

https://github.com/Azure/azure-sdk-for-net/blob/4162f6fa2445b2127468b9cfd080f01c9da88eba/sdk/mgmtcommon/ClientRuntime/ClientRuntime/ServiceClient.cs#L126-L179

https://github.com/Azure/azure-sdk-for-net/blob/4162f6fa2445b2127468b9cfd080f01c9da88eba/sdk/mgmtcommon/ClientRuntime/ClientRuntime/ServiceClient.cs#L584-L600

and a TODO

https://github.com/Azure/azure-sdk-for-net/blob/4162f6fa2445b2127468b9cfd080f01c9da88eba/sdk/mgmtcommon/ClientRuntime/ClientRuntime/ServiceClient.cs#L506

...and, it looks like the actual pattern for using ServiceClient.cs is to rely on some code generation, and I can't seem to figure out where that code generator lives, but I can see the checked-in artifacts:

  1. https://github.com/Azure/azure-sdk-for-net/blob/4162f6fa2445b2127468b9cfd080f01c9da88eba/sdk/cognitiveservices/Search.BingVisualSearch/src/Generated/VisualSearchClient.cs
  2. https://github.com/Azure/azure-sdk-for-net/blob/4162f6fa2445b2127468b9cfd080f01c9da88eba/sdk/hdinsight/Microsoft.Azure.HDInsight.Job/src/Generated/HDInsightJobClient.cs
  3. https://github.com/Azure/azure-sdk-for-net/blob/4162f6fa2445b2127468b9cfd080f01c9da88eba/sdk/cognitiveservices/Search.BingNewsSearch/src/Generated/NewsSearch/NewsSearchClient.cs
  4. https://github.com/Azure/azure-sdk-for-net/blob/4162f6fa2445b2127468b9cfd080f01c9da88eba/sdk/cognitiveservices/Search.BingImageSearch/src/Generated/ImageSearch/ImageSearchClient.cs
  5. https://github.com/Azure/azure-sdk-for-net/blob/4162f6fa2445b2127468b9cfd080f01c9da88eba/sdk/cognitiveservices/Search.BingEntitySearch/src/Generated/EntitySearch/EntitySearchClient.cs
  6. https://github.com/Azure/azure-sdk-for-net/blob/4162f6fa2445b2127468b9cfd080f01c9da88eba/sdk/cognitiveservices/Search.BingVideoSearch/src/Generated/VideoSearch/VideoSearchClient.cs
  7. etc... there's got to be tens if not hundreds of these auto-generated clients.

Point is: The "if you want to see how someone has handled these issues, you can take a look at the Azure SDK. IIRC they've dealt with this" guidance is not good.

I think this is what Joshua Bloch would call the empathy gene importance in good API design. https://www.youtube.com/watch?v=aAb7hSCtvGw

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I filed a ticket with the Azure SDK team and they elaborated on what their solution to Jehoel's Scenario 4 is, where you are a API author providing API services to a client. See: Azure/azure-sdk-for-net#29035 (comment)

@karelz
Copy link
Member

karelz commented Mar 22, 2021

@jzabroski do you plan to take the suggested changes above?

@MSDN-WhiteKnight
Copy link
Contributor

I think this PR was superseded by #3986 and is no longer needed

@jzabroski
Copy link
Author

@MSDN-WhiteKnight I don't think it directly addresses it, actually. I'll need to carefully re-read the article now but it seems neither my initial PR nor Karel comments / idea to improve the documentation actually teach people "how to fish" but rather seems more focused on documentation of footguns that were mysteriously omitted

@ghost ghost removed the needs-author-action An issue or pull request that requires more info or actions from the author. label May 24, 2022
@CarnaViire
Copy link
Member

Hi @jzabroski,
From what I've seen in your comments, they seem to be mostly about separate places in the docs, e.g. ITypedHttpClientFactory (and some comments were implying potential problems in existing implementation).

Could you please

  1. File a separate issue in docs repo with your comments and let's discuss it there and not in a PR
  2. For what you think might be a product issue, please file it in the dotnet/runtime repo

I'm closing this PR as what we were trying to update in this PR was already addressed by #8098 and dotnet/docs#29568

@CarnaViire CarnaViire closed this Jun 22, 2022
@jzabroski
Copy link
Author

Agreed, thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-System.Net.Http ✨ 1st-time dotnet-api-docs contributor! Indicates PRs from new contributors to the dotnet-api-docs repository
Projects
None yet
Development

Successfully merging this pull request may close these issues.