Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does WCF's ClientBase<TChannel> handle TCP connections when Disposed?

A while ago I came across an interesting article explaining that putting HttpClient in a using block will dispose of the object when the code has executed but not close the TCP socket and the TCP state will eventually go to TIME_WAIT and stay in that state listing for further activity for 4 minutes (default).

So basically using this multiple times:

using(var client = new HttpClient())
{
    //do something with http client
}

results in many open TCP connections sitting in TIME_WAIT.

You can read the whole thing here:

You're using HttpClient wrong and it is destabilizing your software

So I was wondering what would happen if I did the same with the ClientBase<TChannel> derived service class created by Visual Studio when you right-click a project and select add Add Service Reference . . and implemented this:

    //SomeServiceOutThere inherits from ClientBase
    using (var service = new SomeServiceOutThere()) 
    {
       var serviceRequestParameter = txtInputBox.Text;
       var result = service.BaddaBingMethod(serviceRequestParameter);
       //do some magic (see Fred Brooks quote)
    }

However, I haven't been able to recreate exactly the same behavior, and I wonder why.

  1. I created a small desktop app and added a reference to a IIS hosted WCF service.
  2. Next I added a button that basically calls the code via the using code block like I showed above.
  3. After hitting the service the first time, I run netsat for the IP and this is the result:

enter image description here

  1. So far so good. I clicked the button again, and sure enough, new connection established, while the first one went into TIME_WAIT state:

enter image description here

  1. However, after this, every time I hit the service it would use the ESTABLISHED connection, and not open any more like in the HttpClient demo (even when passing different parameters to the service, but keeping the app running).

It seems that WCF is smart enough to realize there is already an established connection to the server, and uses that.

The interesting part is that, when I repeated the process above, but stopped and restarted the application between each call to the service, I did get the same behavior as with HttpClient:

enter image description here

There are some other potential problems with ClientBase (e.g. see here), and I know that temporarily open sockets may not be an issue at all if traffic to the service is relatively low or the server is setup for a large number of maximum connections, but I would still like to be able to reliably test whether this could be a problem or not, and under what conditions (e.g. a running windows service hitting the WCF service vs. a Desktop application).

Any thoughts?

like image 293
Duanne Avatar asked Oct 19 '22 00:10

Duanne


1 Answers

WCF does not use HttpClient internally. WCF probably uses HttpWebRequest because that API was available at the time and it's likely a bit faster since HttpClient is a wrapper around it.

WCF is meant for high performance use cases so they made sure that HTTP connections are reused. Not reusing connections by default is, in my mind, unacceptable. This is either a bug or a design problem with HttpClient.

The 4.6.2 Desktop .NET Framework contains this line in HttpClienthandler.Dispose:

ServicePointManager.CloseConnectionGroups(this.connectionGroupName);

Since this code is not in CoreClr there is no documentation for it. I don't know why this was added. It even has a bug because of this.connectionGroupName = RuntimeHelpers.GetHashCode(this).ToString(NumberFormatInfo.InvariantInfo); in the ctor. Two connectionGroupNames can clash. This is a terrible way obtaining random numbers that are supposed to be unique.

If you restart the process there is no way to reuse existing connections. That's why you are seeing the old connections in a TIME_WAIT state. The two processes are unrelated. For what the code in them (and the OS) knows they are not cooperating in any way. It's also hard to save a TCP connection across process restarts (but possible). No app that I know of does this.

Are you starting processes so often that this might become a problem? Unlikely, but if yes you can apply one of the general workaround such as reducing TIME_WAIT duration.

Replicating this is easy: Just start 100k test processes in a loop.

like image 54
usr Avatar answered Oct 31 '22 21:10

usr