tags:

views:

88

answers:

4

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.

A: 

You have put your own answer in the question.

"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"

That's correct.

Applying this to the example you have given, you can simplify it as:

    try
    {
        _service = new BillingServiceClient();
        customers = _service.GetCustomers().ToList();

    if (customers != null)
        foreach (CustomerModel customer in customers)
        {
                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;
     }

Later Edit: Oh, hang on, I just reread your question. You've said that it would fail after multiple repeated calls. I guess the above code actually causes the failure?

Andrew Shepherd
This code will succeed, because I only have a few hundred customer objects. Applying the same model to thousands of invoice lines will fail. I am starting to think I either need to use the ChannelFactory, or to institute a caching mechanism that occasionally resets the service proxy, or to re-create the proxy for every service call. Right now I'm investigating the ChannelFactory further.
Paul Chavez