Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best Practice for WCF proxy lifetime - or how often to close a WCF proxy?

Tags:

I've been working on a WPF application that uses WCF to access the server side logic & database.

I started with a single WCF client proxy object that I was using repeatedly to call methods on the server. After using the proxy for a while, the server would eventually throw an exception: System.ServiceModel.EndpointNotFoundException: There was no endpoint listening at http://.../Service/BillingService.svc that could accept the message. This is often caused by an incorrect address or SOAP action. See InnerException, if present, for more details. ---> System.Net.WebException: Unable to connect to the remote server ---> System.Net.Sockets.SocketException: An operation on a socket could not be performed because the system lacked sufficient buffer space or because a queue was full.

I think this is because every service call was opening a new socket from the proxy to the server and never closing them. Eventually the server was flooded and began refusing requests.

After a brief bit of searching, I determined that I need to Close() the proxy periodically. The samples I found are degenerately small. This one provided some helpful hints, but doesn't really answer the question. I've also seen recommendations to avoid the using() pattern (and apply try/catch/finally instead) because the proxy's Dispose method may throw an exception (yuck).

It seems like the recommended pattern is shaping up like this:

[TestClass]
public class WCFClientUnitTest
{
    BillingServiceClient _service;

    [TestMethod]
    public void TestGetAddressModel()
    {
        List<CustomerModel> customers = null;

        try
        {
            _service = new BillingServiceClient();
            customers = _service.GetCustomers().ToList();
        }
        catch
        {
            _service.Abort();
            _service = null;
            throw;
        }
        finally
        {
            if ((_service != null) &&
                (_service.State == System.ServiceModel.CommunicationState.Opened))
                _service.Close();
            _service = null;
        }

        if (customers != null)
            foreach (CustomerModel customer in customers)
            {
                try
                {
                    _service = new BillingServiceClient();
                    AddressModel address = (AddressModel)_service.GetAddressModel(customer.CustomerID);

                    Assert.IsNotNull(address, "GetAddressModel returned null");
                }
                catch
                {
                    _service.Abort();
                    _service = null;
                    throw;
                }
                finally
                {
                    if ((_service != null) &&
                        (_service.State == System.ServiceModel.CommunicationState.Opened))
                        _service.Close();
                        _service = null;
                }
            }
    }

So my question still revolves around how long should I keep a client proxy alive? Should I open/close it for every service request? That seems excessive to me. Won't I incur a significant performance hit?

What I really want to do is create & open a channel and make brief burst of repeated, short, sequential service calls across the channel. Then nicely close the channel.

As a side note, while I haven't implemented it yet, I will soon be adding a security model to the service (both SSL & ACL) to restrict who can call the service methods. One of the answers to this post mentions that renegotiating the authentication & security context makes reopening the channel for every service call wasteful, but simply recommends to avoid constructing a security context.


EDIT 11/3/2010: This seems important, so I am adding it to the question...

In response to Andrew Shepherd's comment/suggestion, I re-ran my unit test with my TrendMicro AntiVirus shutdown while monitoring the output of netstat -b. Netstat was able to record a significant growth of open ports that were owned by WebDev.WebServer40.exe. The vast majority of the ports were in TIME_WAIT state. Microsoft says that ports may linger in NET_WAIT after the client closes the connection...

NOTE: It is normal to have a socket in the TIME_WAIT state for a long period of time. The time is specified in RFC793 as twice the Maximum Segment Lifetime (MSL). MSL is specified to be 2 minutes. So, a socket could be in a TIME_WAIT state for as long as 4 minutes. Some systems implement different values (less than 2 minutes) for the MSL.

This leads me to believe that if every service call opens a new socket on the server, and because I am calling the service in a tight loop, I could easily flood the server, causing it to run out of available sockets and in turn generate the exception I noted above.

Therefore, I need to pursue one of two paths: 1) attempt to batch service calls so that they reuse the server side socket 2) change my service contract so that I can return larger chunks of data with fewer calls.

The first choice seems better to me, and I am going to continue to pursue it. I'll post back what I discover and welcome further comments, questions and answers.

like image 587
Paul Chavez Avatar asked Oct 28 '10 22:10

Paul Chavez


1 Answers

(Posting a completely different second answer)

If we are talking about "best practice", the real best practice with WCF is to have "coarse-grained methods". If a client is calling a method numerous times in a loop, then the entire business logic should be moved to the service itself.

For example.

[DataContract]
class CustomerAndAddress
{
    [DataMember]
    CustomerModel Customer;

    [DataMember]
    AddressModel Address;
}

[ServiceContract]
class BillingService
{
   [OperationContract]
   CustomerAndAddress[] GetAllCustomersAndAddresses();
}

or, more likely in the real world:

[ServiceContract]
class BillingService
{
   [OperationContract]
   CustomerReportData FetchCustomerReportInfo(CustomerReportParameters parameterSet);
}

Having said that, I'm still interested to see if you can pull off what you are trying.

like image 129
Andrew Shepherd Avatar answered Sep 30 '22 23:09

Andrew Shepherd