Skip to content
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

WCF Client and open connections #4153

Closed
Edminsson opened this issue Feb 6, 2020 · 14 comments
Closed

WCF Client and open connections #4153

Edminsson opened this issue Feb 6, 2020 · 14 comments
Assignees
Labels
customer assistance Help customers with questions regarding usage of WCF features.

Comments

@Edminsson
Copy link

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.

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.

enter image description here

It looks to me like using the WCF client out-of-the-box like this can lead to socket exhaustion.

Following this issue I've managed to inject my own HttpMessageHandler from IHttpMessageHandlerFactory into the WCF client.

My EndpointBehavior looks like this

    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);
        }

        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
        }

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {
        }

        public void Validate(ServiceEndpoint endpoint)
        {
        }
    }

and I add this behavior to my WCF client like this

    public class SomethingService : IImportWithBehaviorService
    {
        private readonly MyEndpoint endpoint;

        public SomethingService(MyEndpoint endpoint)
        {
            this.endpoint = endpoint;
        }

        public async Task<Something> GetSomethingAsync()
        {
            var client = new ImportSoapClient();
            client.Endpoint.EndpointBehaviors.Add(endpoint);
            ...use and close client
        }
    }

This solution seems to solve the problem with open connections.

Is this the proper way to use the WCF client if I want to avoid socket exhaustion?
Is there a better or simpler way?
If this is the way to go I must say it's not very well documented.

@StephenBonikowsky
Copy link
Member

@Edminsson When you create a new client it creates a new channelfactory which creates a new http client instance. When you do that you don't pool your connections between instances.

Create a single client and re-use it.

var client = new ImportSoapClient();

@mconnew as FYI.

@StephenBonikowsky StephenBonikowsky added the customer assistance Help customers with questions regarding usage of WCF features. label Feb 11, 2020
@Edminsson
Copy link
Author

@StephenBonikowsky If I re-use the client I guess I should also handle a faulted channel and recreate the client when that happens?

@StephenBonikowsky
Copy link
Member

Correct.

@mconnew do you have any other coding patterns to suggest here?

@Edminsson
Copy link
Author

Edminsson commented Feb 13, 2020

@StephenBonikowsky And the fact that this is a ASP.NET Core web application with potentially multiple concurrent users and they all share the same client does not matter right?

@mconnew
Copy link
Member

mconnew commented Feb 13, 2020

@Edminsson, it depends on your transport. Sharing the same client is fine for HTTP, but a problem for Net.Tcp. If you are using BasicHttpBinding or NetHttpBinding (and not using WebSockets), the channel shouldn't be able to go into the faulted state as there's no session. Having a channel in the faulted state basically means the underlying session isn't usable anymore. With Net.Tcp this means the socket is disconnected. With WSHttpBinding, this can mean the server was restarted so your persistent session has gone. But with BasicHttpBinding/NetHttpBinding, there's nothing that persists from one call to another so you shouldn't ever see the channel faulted unless your own application code calls Abort on the channel.

Another possibility is you could create a channel proxy instance from the underlying channelfactory. You would do this with code similar to this:

public void Init()
{
    _client?.Close();
    _factory?.Close();
    _client = new ImportSoapClient();
    _factory = client.ChannelFactory;
}

public void DoWork()
{
    var proxy = _factory.CreateChannel();
    proxy.MyOperation();
    ((IClientChannel)proxy).Close();
}

One thing to be aware of, if you are going to go the simple route of sharing the client channel, you must call Open() on the client before you make any calls. If you don't, there is a behavior where if you rely on the channel being implicitly opened, your calls will be made serially one at a time until there are no more outstanding calls on the client. Then it will allow concurrent calls. Basically Thread1 makes the first call which implicitly opens the channel before making the request. Thread2 makes a request using the same client before the response has come back from Thread1 so it queues up the request until Thread1's response has completed, then it makes Thread2's request. It will keep doing this while there's a request in progress. If you explicitly call open before making any service calls, then Thread1 makes a request which is in progress, when Thread2 makes a request before Thread1 has completed, it's request will happen over a new connection in the socket pool of HttpClientHandler and won't wait for Thread1 to complete first.

@Edminsson
Copy link
Author

@mconnew Thank you for the explanation.
I'm using BasicHttpBinding so a faulted channel shouldn't be a problem then.
So, I have the option of reusing the client or the ChannelFactory to accomplish basically the same thing.
Creating a static client, never closing it and never caring of faulted channel is of course very appealing.
I'm assuming that concurrent requests to my ASP.NET Core web application all using the same client is not an issue.
Reusing the ChannelFactory requires slightly more overhead but the client is closed after every request. Are there any advantages to this solution in this particular case?

@mconnew
Copy link
Member

mconnew commented Feb 20, 2020

Concurrent requests all using the same client is not a problem as long as you explicitly open the channel before any requests are made. If using a channel created from the channel factory, you can do this with ((IClientChannel)proxy).Open();. I believe the generated client also adds an OpenAsync method that you can use.

@Edminsson
Copy link
Author

@mconnew this behavior that requires the client to be explicitly open before making any calls, does it also apply to the case @StephenBonikowsky first suggested where I just create the client like this:

var client = new ImportSoapClient();

Or do I only have to open the client first only when creating it like this:

var proxy = _factory.CreateChannel();

@StephenBonikowsky
Copy link
Member

@mconnew Could you take a moment to respond to @Edminsson

@Edminsson
Copy link
Author

@mconnew and @StephenBonikowsky another question: Re-using the WCF client should also mean re-using the HttpClient instance right? And doing that could potentially lead to this the well-known DNS problem (Singleton HttpClient doesn't respect DNS changes) right?

@mconnew
Copy link
Member

mconnew commented Apr 14, 2020

@Edminsson, yes reusing it does reuse the same HttpClient instance. I created a way to modify and/or replace the HttpMessageHandler. A description of some of the details and the following discussion starts here. Someone has written a blog post on how to do this here.

And yes, you should open the generated client. I believe there's an OpenAsync method generated so you should be able to do something like this:

var client = new ImportSoapClient();
await client.OpenAsync();

@Edminsson
Copy link
Author

@mconnew thank you, the circle is now closed since you referenced to the same link that I used in my original question. We ended up creating a WCF Client factory that uses the techniques described in that link and blog post .

@HongGit HongGit closed this as completed Jul 23, 2020
@GarthMartin
Copy link

GarthMartin commented Jun 29, 2021

@mconnew I realize this is a year old, but I am running into much the same issue (connection limits and SNAT port exhaustion) trying to access WCF services from an Azure Function app. Unfortunately I have to use a v1 function app since one of the endpoints only supports MTOM encoding and I cant get that in the v2+ versions.

I have been trying to initialize a single client in my function app startup, so that the underlying connection will be re-used but its not working, and after 20 minutes of load, the function app depletes the available connections and shuts down.

Basically I have 4 clients to create at startup an example of which is:

var docManEndPoint = new EndpointAddress(new Uri(DocumentManagementEndPointAddress));  
var docManBinding = new BasicHttpsBinding(BasicHttpsSecurityMode.Transport)
            {
                MaxReceivedMessageSize = 2147483647,
                UseDefaultWebProxy = false,
                TransferMode = TransferMode.Buffered
            };
  docManBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
  DocManClient = new DocumentManagementClient(docManBinding, docManEndPoint);
  DocManClient.ClientCredentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Impersonation;
  DocManClient.Open();

where "DocManClient" is a global variable.

Would I be better off going the ChannelFactory route you have mentioned above?

I had seen somewhere that connection pooling when using customBindings was not possible, is that truly the case?

I noticed in this post #2161 you mentioned that in Framework it should be using the ServicePointManager to do connection pooling, but is there something that needs to be done to enforce it in this scenario?

Any help is appreciated

@nikriaz
Copy link

nikriaz commented Jan 25, 2024

@Edminsson, yes reusing it does reuse the same HttpClient instance. I created a way to modify and/or replace the HttpMessageHandler. A description of some of the details and the following discussion starts here. Someone has written a blog post on how to do this here.

And yes, you should open the generated client. I believe there's an OpenAsync method generated so you should be able to do something like this:

var client = new ImportSoapClient();
await client.OpenAsync();

Unfortunately, nothing was said about closing connection.
@mconnew and @StephenBonikowsky, may I kindly ask you to clarify what happens with http connection when I use BasicHttpBinding and and a call likeclient.MyActionAsync()is completed? When the connection is closed?

The point is that we can just pool clients. Let's say, we can keep it created for two minutes and then dispose. It looks beneficial compare to injecting custom http handler because we pooling not only connection but the whole client. It looks clearer and we have more control, easier to define number of concurrent calls etc. But the question is whether the underlaid client will keep connection open, so such pooling make sense at all.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
customer assistance Help customers with questions regarding usage of WCF features.
Projects
None yet
Development

No branches or pull requests

6 participants