What is the Correct Way to Dispose a WCF Proxy?
Asked Answered
E

1

8

I have been struggling with WCF Proxies. What is the correct way to Dispose a WCF Proxy? The answer is not trivial.

System.ServiceModel.ClientBase violates Microsoft's own Dispose-pattern

System.ServiceModel.ClientBase<TChannel> does implement IDisposable so one must assume that it should be disposed or used in a using-block. These are best practices for anything disposable. The implementation is explicit, however, so one does have to explicitly cast ClientBase instances to IDisposable, clouding the issue.

The biggest source of confusion, however, is that calling Dispose() on ClientBase instances that faulted, even channels that faulted because they never opened in the first place, will result in an exception being thrown. This, inevitably, means that the meaningful exception explaining the fault is immediately lost when the stack unwinds, the using scope ends and Dispose() throws a meaningless exception saying that you can't dispose a faulted channel.

The above behaviour is anathema to the dispose pattern which states that objects must be tolerant of multiple explicit calls to Dispose(). (see http://msdn.microsoft.com/en-us/library/b1yfkh5e(v=vs.110).aspx, "...allow the Dispose(bool) method to be called more than once. The method might choose to do nothing after the first call.")

With the advent of inversion-of-control, this poor implementation becomes a real problem. I.O.C. containers (specifically, Ninject) detect the IDisposable interface and call Dispose() explicitly on activated instances at the end of an injection scope.

Solution: Proxy ClientBase and Intercept calls to Dispose()

My solution was to proxy ClientBase by subclassing System.Runtime.Remoting.Proxies.RealProxy and to hijack or intercept calls to Dispose(). My first replacement for Dispose() went something like this:

if (_client.State == CommunicationState.Faulted) _client.Abort();
else ((IDisposable)_client).Dispose();

(Note that _client is a typed reference to the target of the proxy.)

Problems with NetTcpBinding

I thought that this had nailed it, initially, but then I discovered a problem in production: under certain scenarios that were fiendishly difficult to reproduce, I found that channels using a NetTcpBinding were not closing properly in the unfaulted case, even though Dispose was being called on _client.

I had an ASP.NET MVC Application using my proxy implementation to connect to a WCF Service using a NetTcpBinding on the local network, hosted within a Windows NT Service on a service cluster with only one node. When I load-tested the MVC Application, certain endpoints on the WCF Service (which was using port-sharing) would stop responding after a while.

I struggled to reproduce this: the same components running across the LAN between two developer's machines worked perfectly; a console application hammering the real WCF endpoints (running on the staging service cluster) with many processes and many threads in each worked; configuring the MVC Application on the staging server to connect to the endpoints on a developer's machine worked under load; running the MVC Application on a developer's machine and connecting to the staging WCF endpoints worked. The last scenario only worked under IIS Express, however, and this was a breakthrough. The endpoints would sieze up when load-testing the MVC Application under full-fat IIS on a developer's machine, connecting to the staging service cluster.

Solution: Close the Channel

After failing to understand the problem and reading many, many pages of the MSDN and other sources that claimed the problem shouldn't exist at all, I tried a long-shot and changed my Dispose() work-around to...

if (_client.State == CommunicationState.Faulted) _client.Abort();
else if (_client.State == CommunicationState.Opened)
{
    ((IContextChannel)_client.Channel).Close();
    ((IDisposable)_client).Dispose();
}
else ((IDisposable)_client).Dispose();

... and the problem stopped occurring in all test setups and under load in the staging environment!

Why?

Can anyone explain what might have been happening and why explicitly closing the Channel before calling Dispose() solved it? As far as I can tell, this shouldn't be necessary.

Finally, I return to the opening question: What is the correct way to Dispose a WCF Proxy? Is my replacement for Dispose() adequate?

Epicarp answered 12/12, 2014 at 10:14 Comment(0)
E
1

The issue, as far as I have been able to understand, is that calling Dispose disposes off the handle, but doesn't actually close the channel, which then holds on to the resources and then eventually times out.

This is why your service stopped responding after a while during load testing: because the initial calls held on to resources longer than you thought they would, and later calls could then not avail those resources.

I came up with the following solution. The premise of the solution is that calling Dispose should be enough to dispose off the handle as well as close the channel. The additional benefit is that if the client ends up in a faulted state, it is recreated so that subsequent calls succeed.

If ServiceClient<TService> is injected into another class via a dependency injection framework like Ninject, then all resources will properly be released.

NB: Please note that in the case of Ninject, the binding must define a scope, i.e., it must not be missing an InXyzScope or be defined with an InTransientScope. If no scope makes sense, then use InCallScope.

Here's what I came up with:

public class ServiceClient<TService> : IDisposable
{
    private readonly ChannelFactory<TService> channelFactory;

    private readonly Func<TService> createChannel;

    private Lazy<TService> service;

    public ServiceClient(ChannelFactory<TService> channelFactory)
        : base()
    {
        this.channelFactory = channelFactory;

        this.createChannel = () =>
        {
            var channel = ChannelFactory.CreateChannel();

            return channel;
        };

        this.service = new Lazy<TService>(() => CreateChannel());
    }

    protected ChannelFactory<TService> ChannelFactory
    {
        get
        {
            return this.channelFactory;
        }
    }

    protected Func<TService, bool> IsChannelFaulted
    {
        get
        {
            return (service) =>
                {
                    var channel = service as ICommunicationObject;

                    if (channel == null)
                    {
                        return false;
                    }

                    return channel.State == CommunicationState.Faulted;
                };
        }
    }

    protected Func<TService> CreateChannel
    {
        get
        {
            return this.createChannel;
        }
    }

    protected Action<TService> DisposeChannel
    {
        get
        {
            return (service) =>
                {
                    var channel = service as ICommunicationObject;

                    if (channel != null)
                    {
                        switch (channel.State)
                        {
                            case CommunicationState.Faulted:
                                channel.Abort();
                                break;

                            case CommunicationState.Closed:
                                break;

                            default:
                                try
                                {
                                    channel.Close();
                                }
                                catch (CommunicationException)
                                {
                                }
                                catch (TimeoutException)
                                {
                                }
                                finally
                                {
                                    if (channel.State != CommunicationState.Closed)
                                    {
                                        channel.Abort();
                                    }
                                }
                                break;
                        }
                    }
                };
        }
    }

    protected Action<ChannelFactory<TService>> DisposeChannelFactory
    {
        get
        {
            return (channelFactory) =>
                {
                    var disposable = channelFactory as IDisposable;

                    if (disposable != null)
                    {
                        disposable.Dispose();
                    }
                };
        }
    }

    public void Dispose()
    {
        DisposeChannel(this.service.Value);
        DisposeChannelFactory(this.channelFactory);
    }

    public TService Service
    {
        get
        {
            if (this.service.IsValueCreated && IsChannelFaulted(this.service.Value))
            {
                DisposeChannel(this.service.Value);

                this.service = new Lazy<TService>(() => CreateChannel());
            }

            return this.service.Value;
        }
    }
}
Emotionality answered 14/8, 2015 at 8:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.