This article shows a well-known problem with HttpClient that can lead to socket exhaustion.
I have an ASP.NET Core 3.1 web application. In a .NET Standard 2.0 class library I've added a WCF web service reference in Visual Studio 2019 following this instructions.
In a service I'm using the WCF client the way it's described in the documentation. Creating an instance of the WCF client and then closing the client for every request.
public class TestService
{
public async Task<int> Add(int a, int b)
{
CalculatorSoapClient client = new CalculatorSoapClient();
var resultat = await client.AddAsync(a, b);
//this is a bad way to close the client I should also check
//if I need to call Abort()
await client.CloseAsync();
return resultat;
}
}
I know it's bad practice to close the client without any checks but for the purpose of this example it does not matter.
When I start the application and make five requests to an action method that uses the WCF client and then take a look at the result from netstat I discover open connections with status TIME_WAIT, much like the problems in the article above about HttpClient.
It looks to me like using the WCF client out-of-the-box like this can lead to socket exhaustion or am I missing something?
The WCF client inherits from ClientBase<TChannel>
. Reading this article it looks to me like the WCF client uses HttpClient. If that is the case then I probably shouldn't create a new client for every request, right?
I've found several articles (this and this) talking about using a singleton or reusing the WCF client in some way. Is this the way to go?
###UPDATE
Debugging the appropriate parts of the WCF source code I discovered that a new HttpClient and HttpClientHandler were created each time I created a new WCF client which I do for every request. You can inspect the code here
internal virtual HttpClientHandler GetHttpClientHandler(EndpointAddress to, SecurityTokenContainer clientCertificateToken)
{
return new HttpClientHandler();
}
This handler is used in to create a new HttpClient in the GetHttpClientAsync method:
httpClient = new HttpClient(handler);
This explains why the WCF client in my case behaves just like a HttpClient that is created and disposed for every request.
Matt Connew writes in an issue in the WCF repo that he has made it possible to inject your own HttpMessage factory into the WCF client. He writes:
I implemented the ability to provide a Func<HttpClientHandler, HttpMessageHandler> to enable modifying or replacing the HttpMessageHandler. You provide a method which takes an HttpClientHandler and returns an HttpMessageHandler.
Using this information I injected my own factory to be able to control the generation of HttpClientHandlers in HttpClient.
I created my own implementation of IEndpointBehavior that injects IHttpMessageHandlerFactory to get a pooled HttpMessageHandler.
public class MyEndpoint : IEndpointBehavior
{
private readonly IHttpMessageHandlerFactory messageHandlerFactory;
public MyEndpoint(IHttpMessageHandlerFactory messageHandlerFactory)
{
this.messageHandlerFactory = messageHandlerFactory;
}
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
Func<HttpClientHandler, HttpMessageHandler> myHandlerFactory = (HttpClientHandler clientHandler) =>
{
return messageHandlerFactory.CreateHandler();
};
bindingParameters.Add(myHandlerFactory);
}
<other empty methods needed for implementation of IEndpointBehavior>
}
As you can see in AddBindingParameters I add a very simple factory that returns a pooled HttpMessageHandler.
I add this behavior to my WCF client like this.
public class TestService
{
private readonly MyEndpoint endpoint;
public TestService(MyEndpoint endpoint)
{
this.endpoint = endpoint;
}
public async Task<int> Add(int a, int b)
{
CalculatorSoapClient client = new CalculatorSoapClient();
client.Endpoint.EndpointBehaviors.Add(endpoint);
var resultat = await client.AddAsync(a, b);
//this is a bad way to close the client I should also check
//if I need to call Abort()
await client.CloseAsync();
return resultat;
}
}
Be sure to update any package references to System.ServiceModel.*
to at least version 4.5.0 for this to work. If you're using Visual Studio's 'Add service reference' feature, VS will pull in the 4.4.4 versions of these packages (tested with Visual Studio 16.8.4).
When I run the applications with these changes I no longer have an open connection for every request I make.