简体   繁体   中英

WCF memory leak when calling WCF service from within another WCF service

I have noticed a memory leak issue in a WCF application and have managed to replicate it in a simple program. The issue occurs when calling a WCF service from within another WCF service.

In the following example, I have two services A and B . When I call the DoWork method on service A , it in turn calls the DoWork method of service B .

In the example that follows, I am creating a new ChannelFactory each time, opening a channel using it, calling DoWork and then disposing of the channel and the factory at the end. This way, the process starts leaking memory.

If I set either of the calls, or both, to reuse the same ChannelFactory each time (comment and uncomment the marked lines in the example) the leak stops.

If I keep creating a new ChannelFactory each time I call service A , but blank out ServiceA 's DoWork method (so it does not call ServiceB ) the leak doesn't happen.

I am running the program targeted for .NET 3.5. Weirdly, if I switch to .NET 4, 4.5 or 4.5.1, the process leaks memory faster.

Can anyone understand why this is happening and maybe how to fix it (or at least work around it)?

The example code follows:

using System;
using System.ServiceModel;

namespace memoryleak
{
    internal class Program
    {
        private static void Main()
        {
            using (var hostA = new ServiceHost(new ServiceA(), new Uri("net.pipe://localhost")))
            using (var hostB = new ServiceHost(new ServiceB(), new Uri("net.pipe://localhost")))
            {
                hostA.AddServiceEndpoint(typeof (ContractA), new NetNamedPipeBinding(), "test_service_a");
                hostA.Open();
                hostB.AddServiceEndpoint(typeof (ContractB), new NetNamedPipeBinding(), "test_service_b");
                hostB.Open();

                while(true)dowork();
            }
        }

        //CALLING SERVICE A

        //uncomment the following line to reuse the same ChannelFactory each time
        //private static readonly ChannelFactory<ContractA> pipeFactory=new ChannelFactory<ContractA>(new NetNamedPipeBinding(),new EndpointAddress("net.pipe://localhost/test_service_a"));

        private static void dowork()
        {
            //comment the following line to reuse the same ChannelFactory each time
            var pipeFactory = new ChannelFactory<ContractA>(new NetNamedPipeBinding(), new EndpointAddress("net.pipe://localhost/test_service_a"));

            ContractA provider = null;
            try
            {
                provider = pipeFactory.CreateChannel();
                provider.DoWork();
            }
            catch
            {
            }
            finally
            {
                CloseChannel(provider);

                //comment the following line to reuse the same ChannelFactory each time
                try { pipeFactory.Close(); }catch{pipeFactory.Abort();}
            }
        }

        private static void CloseChannel(ContractA provider)
        {
            try
            {
                if (provider == null)
                    return;
                try
                {
                    ((IClientChannel) provider).Close();
                }
                catch
                {
                    ((IClientChannel) provider).Abort();
                }
                ((IDisposable) provider).Dispose();
            }
            catch (Exception ex)
            {
                throw new Exception("Error while closing channel", ex);
            }
        }
    }

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]
    public class ServiceA : ContractA
    {
        //CALLING SERVICE B


        //uncomment the following line to reuse the same ChannelFactory each time
        //private readonly ChannelFactory<ContractB> pipeFactory=new ChannelFactory<ContractB>(new NetNamedPipeBinding(),new EndpointAddress("net.pipe://localhost/test_service_b"));

        public void DoWork()
        {
            //comment the following line to reuse the same ChannelFactory each time
            var pipeFactory=new ChannelFactory<ContractB>(new NetNamedPipeBinding(),new EndpointAddress("net.pipe://localhost/test_service_b"));

            ContractB provider = null;
            try
            {
                provider = pipeFactory.CreateChannel();
                provider.DoWork();
            }
            catch
            {
            }
            finally
            {
                CloseChannel(provider);

                //comment the following line to reuse the same ChannelFactory each time
                try { pipeFactory.Close(); } catch { pipeFactory.Abort(); }
            }
        }

        private void CloseChannel(ContractB provider)
        {
            try
            {
                if (provider == null)
                    return;
                try
                {
                    ((IClientChannel) provider).Close();
                }
                catch
                {
                    ((IClientChannel) provider).Abort();
                }
                ((IDisposable) provider).Dispose();
            }
            catch (Exception ex)
            {
                throw new Exception("Error while closing channel", ex);
            }
        }

    }

    [ServiceContract]
    public interface ContractA
    {
        [OperationContract]
        void DoWork();
    }

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]
    public class ServiceB : ContractB
    {
        public void DoWork()
        {
        }
    }

    [ServiceContract]
    public interface ContractB
    {
        [OperationContract]
        void DoWork();
    }
}

What may be happening is that you are creating objects faster than the default workstation garbage collector can get rid of them. The default latency mode for the .net 3.5 GC is interactive which means it gives up if collecting take too long for the sake of keeping the UI responsive. The GC works differently in .net 4 & 4.5 which is probably why you see different rates of growth.

Try turning on server garbage collection mode in your App.config and see if the behavior changes. This should allow the GC to work until it is done.

<configuration>
   <runtime>
      <gcServer enabled="true"/>
   </runtime>
</configuration>

I'm assuming that you are using a multiple core system, otherwise this will have zero effect.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM