Search code examples
c#autofacidisposable

IDisposable interface confirmation


We have extensivelly read this:

https://autofaccn.readthedocs.io/en/latest/lifetime/disposal.html?highlight=Dispose

And we are aware of scopes, and ExternallyOwned functionality. I have read some posts but are still unsure of one thing and just need confirmation.

I have one DisposableClass that implements IDisposable and one NotDisposableClass.

Given the following code:


        private IContainer _container;
        private ILifetimeScope _scope1;
        private ILifetimeScope _scope2;

        public Form1()
        {
            InitializeComponent();

            ContainerBuilder builder = new ContainerBuilder();
            builder.RegisterType<DisposableClass>();
            builder.RegisterType<NotDisposableClass>();
            _container = builder.Build();
        }

        // Case 1: When finished, only coso2 is cleaned from memory.
        private void Case1()
        {
            DisposableClass coso1 = _container.Resolve<DisposableClass>();
            coso1.DoSomething();
            
            NotDisposableClass coso2 = _container.Resolve<NotDisposableClass>();
            coso2.DoSomething();
        }

        // Case 2: When finished, only coso2 is cleaned from memory.
        private void Case2()
        {
            DisposableClass coso1 = _container.Resolve<DisposableClass>();
            coso1.DoSomething();
            coso1.Dispose();
            
            NotDisposableClass coso2 = _container.Resolve<NotDisposableClass>();
            coso2.DoSomething();
        }

        // Case 3: Both coso1 and coso2 are released from memory.
        private void Case3()
        {
            using (var scope = _container.BeginLifetimeScope())
            {
                DisposableClass coso1 = scope.Resolve<DisposableClass>();
                coso1.DoSomething();
                coso1.Dispose();
            
                NotDisposableClass coso2 = scope.Resolve<NotDisposableClass>();
                coso2.DoSomething();
            }
        }

        // Case4: Both coso1 and coso2 are released.
        private void Case4()
        {
            _scope1 = _container.BeginLifetimeScope();
            DisposableClass coso1 = _scope1.Resolve<DisposableClass>();
            coso1.DoSomething();
            coso1.Dispose();
            
            _scope2 = _container.BeginLifetimeScope();
            NotDisposableClass coso2 = _scope2.Resolve<NotDisposableClass>();
            coso2.DoSomething();

            _scope1.Dispose();
        }

I was not expecting the results from Case1 and Case2 and these results are causing a huge memory leak issue in a much more complex application which make extensive use of autofac. Noone in the team was expecting that a reference to a not scoped variable could be maintained by the container when the class implements IDisposable (Case1), even when Disposing it (Case2).

Registering as ExternallyOwned is not really a solution because Modules registering Types do not really know, how, when, or where that type is going to be used, except maybe for those singleton factories. This means that every module should register ALL types as ExternallyOwned, which is like removing half of the autofac functionality.

Using lifetime scopes are a good solution for transactional operations, web apis and small lifetime operations, but there are many cases where its usage is not very beutiful. Lets say you have a sequence of items that round periodically and every step requires create a new instance of a Disposable object. For this we use SingleInstanceFactories that are actually living in the top builder scope. Them we have noticed that every item created by this factory that make use of container Resolve, is not really released from memory even though you close the window this sequence was created on, not even when calling Dispose.

We kind of discuss possible solutions and we came up to pass a new scope to each factory per creation request and store this scope as well as the requested instance but this seems quite a lot of work. Because you have to manage in a dictionary all scopes used and which instance requested it belongs to.

For me, when the scope of a variable is released, which is the case of Case1 and Case2, the variable should be disposed as the .NET framework would do by itself (same way that coso2 is released in all cases). Or at least, be aware that in case2 we are forcing a Dispose() to a InsntancePerDependency object instance and release it from memory afterwards.

It does not matter how many times you force the GC, the variable is not released from memory until you dispose the container which in front end applications is not easy to handle.

So to summarize:

  • is Case 1 and Case 2 the expected behaviour of object disposal in Autofac? or it is a bug?
  • Is there a way to tell the container to free resources without disposing it? or a specific instance created by it?

Solution

  • For the sake of completion I will answer my question as I already know it.

    It was pretty helpfull to read the following link: https://nblumhardt.com/2011/01/an-autofac-lifetime-primer/

    • is Case 1 and Case 2 the expected behaviour of object disposal in Autofac? or it is a bug?

    It is Autofac normal behaviour. Aufofac will hold a reference to any IDisposable instance created by it and for this reason the GC will not free that resource until the autofac container at that scope is released. It is important to be aware of this because you might think that the Net framework is going to release those variables, as their scope in the code has ended. In the other hand, It could be a good feature if Autofac would be able to notice if it is the only one holding a reference to an instance and release it.

    • Is there a way to tell the container to free resources without disposing it? or a specific instance created by it?

    There is no way to force releasing of a specific instance created by the container. The only available option availabe is to follow the recomendation, use BeginLifeTimeScope before any container resolve invocation and hold somehow a reference to the lifetime scope to release it when needed.

    This could be also a good feature though.

    Thanks for the answers and sorry if some part of the text was not straight forward.