Home > Back-end >  Using IHttpClientFactory to immediately close connections
Using IHttpClientFactory to immediately close connections

Time:02-25

I'm connecting to an API, whos' owner doesn't allow more than 1 request per connection. This is very unfortunate, but I have to work with this. I could create a new HttpClient for each request, but this would leave me vulnerable for socket exhaustion. As such, I'm setting for my HttpClient:

DefaultRequestHeaders.ConnectionClose = true;

And getting it from IHttpClientFactory via

_httpClientFactory.CreateClient();

Now this mostly works fine, but there is one issue. If I send subsequent calls in a loop, like so:

        for (int i = 0; i < 10; i  )
        {
            var request = _requestBuilder.BuildRequest();
            var httpClient = _httpClientFactory.CreateClient();
            var response = await httpClient.SendAsync(request);
        }

Then this will sometimes fail trying to reuse the old connection before it's closed. So my first question is: why is IHttpClientFactory reusing the old connection if I send the request with a Connection: close header?

What's more, I've been looking at netstat and while, unsuprisingly, after creating a new HttpClient for each call I get a bunch of connections in TIME_WAIT status. But for the above ,if it doesn't fail, I get a single connection in CLOSE_WAIT. If I query netstat during runtime of these requests I can see the port is being changed for the current connection, but if a new connection is opened the old one is closed immediately and it doesn't hang in any status. So how is the IHttpClientFactory actually managing this? I couldn't get to close the connection immediately by calling dispose on either HttpClient or HttpResponseMessage

CodePudding user response:

I could create a new HttpClient for each request, but this would leave me vulnerable for socket exhaustion.

I recommend you use an HttpClient for each request. If the server honors ConnectionClose (which it seems to be doing), then you won't be in any more danger of socket exhaustion than you would be using IHttpClientFactory.

For modern HTTP servers (ones that allow multiple requests, like was added in HTTP 1.1... in 1997), there's a danger of socket exhaustion because the clients normally close their sockets, which enter TIME_WAIT state. So IHttpClientFactory avoids the socket exhaustion by reusing the sockets. But in your case, the server won't allow socket reuse, so IHttpClientFactory is just going to add complexity without providing any benefit.

DefaultRequestHeaders.ConnectionClose = true;

Setting this for HTTP connections means that the server closes the socket connection, which means that your local socket enters CLOSE_WAIT rather than TIME_WAIT. Here's the diagram if this isn't clear; it's from Stevens' TCP/IP Illustrated:

TCP/IP state diagram

Then this will sometimes fail trying to reuse the old connection before it's closed. So my first question is: why is IHttpClientFactory reusing the old connection if I send the request with a Connection: close header?

The whole point of IHttpClientFactory is to reuse connections. AFAIK the factory does not have any special handling around ConnectionClose, so it assumes the socket is still viable so it keeps it around. Then it tries to use that socket again.

The "sometimes" part of the failure could be due to a race condition: I suspect the factory is doing something like checking whether the socket is closed, and if it is, creating a new one; and if not, returning a socket (in the CLOSE_WAIT state), which will then fail.

But for the above ,if it doesn't fail, I get a single connection in CLOSE_WAIT.

I would expect the factory to keep its socket connection around, since it's not aware of ConnectionClose.

If I query netstat during runtime of these requests I can see the port is being changed for the current connection, but if a new connection is opened the old one is closed immediately and it doesn't hang in any status.

Connections cannot change ports. I expect the old connection is kept around until it is closed by the factory when it determines the connection isn't viable, and then the factory immediately creates a new one.

I couldn't get to close the connection immediately by calling dispose on either HttpClient or HttpResponseMessage

Technically, the thing that the factory reuses is not HttpClient, but the last HttpMessageHandler in the chain of handlers wrapped by the HttpClient. And for factory-created clients, this isn't disposed when you dispose HttpClient.

  • Related