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

Memory leak in Wcf integration #3

Closed
i-sinister opened this issue May 21, 2015 · 4 comments
Closed

Memory leak in Wcf integration #3

i-sinister opened this issue May 21, 2015 · 4 comments
Labels

Comments

@i-sinister
Copy link

We discovered memory leak in autofac wcf integration - if exception occurs during service resolution ILifetimeScope (and hence all dependencies resolved till exception thrown) are not disposed.
More detailed:

  • Resource class implements IDisposable
  • BadDependency class has Resource dependency; BadDependency 'accesses' Resource in constructor
  • TerribleDependency throws exception in the c-tor
  • Service "Service" with 2 dependencies BadDependency and TerribleDependency.

As a result Resource is never disposed.

This happens because autofac creates ILifetimeScope for the request, however it is not disposed because disposing implemented in AutofacInstanceProvider.ReleaseInstance which is not invoked if exception is thrown in AutofacInstanceProvider.GetInstance.

Here is sample code:

namespace AutofacWcfLeak
{
    using System;
    using System.Net.Http;
    using System.Net.Security;
    using System.ServiceModel;
    using System.ServiceModel.Description;
    using System.ServiceModel.Web;
    using System.Threading;
    using Autofac;
    using Autofac.Integration.Wcf;
    static class Log
    {
        public static void Write(string format, params object[] arguments)
        {
            Console.WriteLine(format, arguments);
        }
    }
    class Resource : IDisposable
    {
        public Resource()
        {
            Log.Write("Resource.ctor");
        }
        public string Consume()
        {
            Log.Write("Resource.Consume");
            return "a";
        }
        public void Dispose()
        {
            Log.Write("Resource.Dispose");
        }
    }
    class BadDependency
    {
        public BadDependency(Resource resource)
        {
            Log.Write("BadDependency.ctor");
            resource.Consume();
        }
    }
    class WorstDependency
    {
        public WorstDependency()
        {
            Log.Write("WorstDependency.ctor");
            throw new Exception("It is a good day to die");
        }
    }
    [ServiceBehavior(
        InstanceContextMode = InstanceContextMode.PerCall,
        ConcurrencyMode = ConcurrencyMode.Multiple)]
    [ServiceContract(ProtectionLevel = ProtectionLevel.None)]
    class Service
    {
        public Service(
            BadDependency badDependency,
            WorstDependency worstDependency)
        {
            Log.Write("Service.ctor");
        }
        [OperationContract]
        [WebGet(UriTemplate = "Ping", ResponseFormat = WebMessageFormat.Json)]
        public string Ping()
        {
            Log.Write("Service.Ping");
            return "pong";
        }
    }
    class Program
    {
        static void Main()
        {
            const string ServiceAddress = "http://localhost:8080/autofac/wcf/leak";
            var container = CreateServiceContainer();
            var host = CreateServiceHost(container, ServiceAddress);
            host.Open();
            try
            {
                Log.Write(
                    "Server says: {0}",
                    new HttpClient().GetStringAsync(new Uri(ServiceAddress + "/Ping")).Result);
            }
            catch (Exception ex)
            {
                Log.Write(ex.ToString());
            }
            // give a chance to wcf to release instance context
            Thread.Sleep(1);
            host.Close();
        }
        private static ServiceHost CreateServiceHost(
            IContainer container,
            string address)
        {
            var baseAddress = new Uri(address);
            var binding =
                new WebHttpBinding
                {
                    Security =
                        new WebHttpSecurity
                        {
                            Mode = WebHttpSecurityMode.None
                        }
                };
            var host = new ServiceHost(typeof(Service));
            var endPoint = host.AddServiceEndpoint(typeof(Service), binding, baseAddress);
            endPoint.Behaviors.Add(new WebHttpBehavior());
            host.AddDependencyInjectionBehavior<Service>(container);
            return host;
        }
        private static IContainer CreateServiceContainer()
        {
            var builder = new ContainerBuilder();
            builder.RegisterType<Service>().AsSelf();
            builder.RegisterType<Resource>().AsSelf().InstancePerLifetimeScope();
            builder.RegisterType<BadDependency>().AsSelf();
            builder.RegisterType<WorstDependency>().AsSelf();
            return builder.Build();
        }
    }
}
@i-sinister
Copy link
Author

To fix issue AutofacInstanceProvider.GetInstance should be changed to smth like this:

public object GetInstance(InstanceContext instanceContext, Message message)
{
    if (instanceContext == null)
    {
        throw new ArgumentNullException("instanceContext");
    }
    AutofacInstanceContext autofacInstanceContext = new AutofacInstanceContext(this._rootLifetimeScope);
    instanceContext.Extensions.Add(autofacInstanceContext);
    try
    {
        return autofacInstanceContext.Resolve(this._serviceData);
    }
    catch
    {
        autofacInstanceContext.Dispose();
        instanceContext.Extensions.Remove(autofacInstanceContext);
        throw;
    }
}

@i-sinister
Copy link
Author

And here is workaround without touching Autofac.Wcf source code:


    internal class AutofacLifetimeScopeDisposingBehavior : IServiceBehavior
    {
        public void Validate(
            ServiceDescription description,
            ServiceHostBase host) { }
        public void AddBindingParameters(
            ServiceDescription description,
            ServiceHostBase host,
            Collection<ServiceEndpoint> endpoints,
            BindingParameterCollection bindingParameters) { }
        public void ApplyDispatchBehavior(
            ServiceDescription description,
            ServiceHostBase host)
        {
            foreach (ChannelDispatcher channelDispatcher in host.ChannelDispatchers)
            {
                foreach (var endpointDispatcher in channelDispatcher.Endpoints)
                {
                    var interceptor = new AutofacLifetimeScopeDisposingHandler();
                    endpointDispatcher
                        .DispatchRuntime
                        .InstanceContextInitializers
                        .Add(interceptor);
                    foreach (var operation in endpointDispatcher.DispatchRuntime.Operations)
                    {
                        operation.CallContextInitializers.Add(interceptor);
                    }
                }
            }
        }
    }
    class AutofacLifetimeScopeDisposingExtension : IExtension<InstanceContext>
    {
        private InstanceContext context;
        private AutofacInstanceContext autofacContext;
        public void Attach(InstanceContext owner)
        {
            context = owner;
            // during InstanceContext.Closed event InstanceContext is already
            // disposed and we can not access its extensions; so we will
            // subscribe to Closing event to get AutofacInstanceContext
            context.Closing += OnClosing;
        }
        public void Detach(InstanceContext owner)
        {
            context.Closing -= OnClosing;
            context = null;
        }
        private void OnClosed(object sender, EventArgs e)
        {
            context.Closed -= OnClosed;
            if (null != autofacContext)
            {
                autofacContext.Dispose();
                autofacContext = null;
            }
            context = null;
        }
        private void OnClosing(object sender, EventArgs e)
        {
            context.Closing -= OnClosing;
            // if we are here it means there was an error during
            // InstanceContext.GetInstance and AutofacInstanceContext
            // should be disposed manually;
            autofacContext = context.Extensions.Find<AutofacInstanceContext>();
            context.Closed += OnClosed;
        }
    }
    class AutofacLifetimeScopeDisposingHandler :
        IInstanceContextInitializer,
        ICallContextInitializer
    {
        public void Initialize(InstanceContext instanceContext, Message message)
        {
            // add extension to handle context disposing
            instanceContext.Extensions.Add(new AutofacLifetimeScopeDisposingExtension());
        }
        public object BeforeInvoke(
            InstanceContext instanceContext,
            IClientChannel channel,
            Message message)
        {
            // ICallContextInitializer.BeforeInvoke is called after
            // service instance was created; as instance created
            // we should cancell custom ILifetimeScope disposing
            var disposer = instanceContext
                .Extensions
                .Find<AutofacLifetimeScopeDisposingExtension>();
            instanceContext.Extensions.Remove(disposer);
            return instanceContext;
        }
        public void AfterInvoke(object correlationState) { }
    }

And then (for self hosting):

host.Description.Behaviors.Add(
    new AutofacLifetimeScopeDisposingBehavior());

For IIS/WAS you should register behavior with configuration

@tillig
Copy link
Member

tillig commented May 22, 2015

Nice catch. When we get finished fighting DNX support into submission, we can take a look at this. Thanks!

@tillig tillig added the bug label May 22, 2015
alexmg added a commit that referenced this issue Jun 17, 2015
…fetime scope immediately when an exception is thrown in the constructor of the service being resolved.
@alexmg
Copy link
Member

alexmg commented Jun 17, 2015

Thanks for the detailed bug report. I have pushed 3.0.2 to NuGet with the code fix in AutofacInstanceProvider.

@alexmg alexmg closed this as completed Jun 17, 2015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants