In last few articles, we have been looking at how HttpClient can be used in .NET applications.
We already know that creating new HttpClient instance for every request is not really recommended, as that might exhaust the resources on server. This might result in slow responses from backend HTTP APIs, or even timeouts. But if the HttpClient is singleton (or static), then it would not respect DNS changes.
In this article, let’s have a look at internals of DefaultHttpClientFactory implementation, and how does it try to solve two issues mentioned above.
Resource Exhaustion Issue
This DefaultHttpClientFactory the default implementation of IHttpClientFactory interface. The code for this can be found on GitHub.
If we look at the CreateClient implementation, below is the logic to create HttpClient instance:
- It makes a call to
CreateHandleris responsible for providing
ActiveHandlerTrackingEntryinstance. But the interesting thing is, these instances are managed as a Dictionary. Key in this dictionary is the name of HttpClient’s logical instance and value is
- Note that
CreateHandlercalls GetOrAdd(TKey, Func<TKey,TValue>) method. It returns existing instance of
ActiveHandlerTrackingEntryfor the given key. If it is not present, then a new instance is created using Func delegate.
Once the handler to use is decided, it is passed for constructing
disposeHandler parameter is set to false, meaning these handlers are maintained in a pool inside
What does this mean ? This means that if
CreateClient method is called multiple times to get a named client “github“, every time the
HttpClient instance would be new. But the underlying handlers pipeline instances would be same, thus avoiding resource exhaustion. But HttpClient instance created for named client “googleMapsApi”, would be have different instances of http message handlers. This is applicable to not only named clients but also typed clients.
So, it means that an ActiveHandlerTrackingEntry is created per named client. But how is this related to lifetime management of resources? Let’s first see what is inside an ActiveHandlerTrackingEntry.
Create Handler Entry
This is the method that is called to create a new
ActiveHandlerTrackingEntry instance if it does not exist in the ConcurrentDictionary. This is very complicated method and high level overview of the logic is given below:
- It creates a new scope object, so that scoped object instances can be managed properly
- Next, it gets an instance of DefaultHttpMessageHandlerBuilder which will create an instance of HttpClientHandler. This HttpClientHandler is responsible for creating sockets connection and making the requests.
IHttpMessageHandlerBuilderFiltercollection injected in DefaultHttpClientFactory is used to create additional
- Thus a whole pipeline of HttpMessageHandlers is created. This pipeline is then wrapped inside LifetimeTrackingHttpMessageHandler. This handler is just a marker handler which is used while cleaning up the resources.
if you want to know more details about internals, please refer this post from Andrew Lock.
Coming Back to Points
Ok, so coming back to original issues –
- Resource Exhaustion due to many instances of
- Issues for respecting DNS modifications (stale DNS) in case we decide to use single instance of
Resource exhaustion issue is solved by pooling the pipelines of handlers. Same handler instances are used for two instances of
HttpClient, as long as they are referring to a same named client.
DNS modifications might lead to stale data if single instance of
HttpClient is used. Stale DNS problems by cycling
HttpMessageHandler instances at regular intervals.
By default, the http message handlers has default lifetime of 2 minutes. So, after every two minutes, the instances of http message hander pipeline is recycled.
Generally, this lifetime duration should be enough for most of the applications. If required, this lifetime duration can be changed via AddHttpClient middleware as shown in below code.
I hope this information is useful. Let me know your thoughts.