What does ReleaseExport really do?

Sep 25, 2010 at 2:18 AM
Edited Sep 25, 2010 at 10:57 PM

The answer appears simple looking at the source code:

 

        public void ReleaseExport(Export export)
        {
            Requires.NotNull(export, "export");

            IDisposable dependency = export as IDisposable;

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

 

Export is declared like:

    [PartCreationPolicy(CreationPolicy.NonShared)]
    [Export("Bar", typeof(UserControl))]
    [ExportMetadata(some metadata...)]
    public partial class Bar : UserControl, IDisposable
    {
        public Bar()
        {
            InitializeComponent();
        }

        public void Dispose()
        {
            DataContext = null;
        }

}

I get all such exports using:

 

               var matching = container.GetExports(
                        new ContractBasedImportDefinition(
                            "Bar",
                            AttributedModelServices.GetTypeIdentity(typeof(UserControl)),
                            metadata,
                            ImportCardinality.ZeroOrMore,
                            false,
                            false,
                            CreationPolicy.NonShared));

 

where matching is IEnumerable<Export> and I iterate through it to find the matching metadata. When I find the metadata I instantiate Bar via:

Bar myBar = bar.Value as Bar;

I would expect ReleaseExport to have code like:

           // some magick to get rid of a reference to export.Value that container may be holding;

rather than:

 	  IDisposable dependency = export as IDisposable;

 

dependency = export as IDisposable == null in my case so ReleaseExport does nothing!

 

Since container is creating Export objects, I have no idea what mechanism makes some Export instance derived from IDisposable or not.

What am I missing here?

The problem I am having is figuring out why my Bar class instances don't get collected even when I insert GC.Collect() calls in the code after calling ReleaseExport. I was under the impression that container holding a last reference to a Bar class instance was the cause and that calling  ReleaseExport would solve it.

Sep 26, 2010 at 2:56 PM

I am very curious about this myself, and I do not understand truly why MEF facilities this kind of behavior. It feels very counter intuitive when you think about the automatic memory management. I'd really like to have Wes or Glenn weigh in on this and sort out all myths about the releasing of exports and the disposable usage.

To my knowledge it's the Export/ExportFactory classes that implement IDisposable and it's the container that tracks the references, if you dispose the container as well, is everything collected as it should?

Possibly, you could try a memory profiler and see what references are being held with that, it might help answer you question.

Sep 26, 2010 at 5:30 PM

Everything is collected if I dispose the container.

I am still struggling with WinDbg to track the references. Error is probably somewhere on my side.

However, after reading all that was written about ReleaseExport here, then looking at the implementation, made things only more confusing. 

Sep 26, 2010 at 6:47 PM

Hammet can weigh in on this one.

Oct 2, 2010 at 7:41 AM

Where is this Hammet guy?

Developer
Oct 2, 2010 at 4:11 PM

Let me see if I can help explain this and then help Tonko with his particular problem.

The job of ReleaseExport is to cleanup a particular export and it's dependencies from the container early, meaning before disposing of the container itself which will cleanup and dispose all objects constructed by the container. Now calling ReleaseExport will do different things depending on the particular export, for example for a Shared export it will actually do nothing but for a NonShared export it will release it and walk its dependency graph releasing them but again it will stop and do nothing for any dependencies that are Shared exports. 

Looking at the implementation details of ReleaseExport as you point out it simply calls Dispose on the export object if it implements IDisposable, this is an implementation detail of the ExportProviders and unless you are writing an ExportProvider I would just forget about this detail. As an example here our CatalogExportProvider will give you a disposable export for NonShared exports it constructs and a non-disposable export for the shared exports it constructs. Our ComposablePartExportProvider, the guys that handles objects directly added to the container via CompositionBatch/Compose, will always give you a non-disposable export because for these you need to explicitly remove the guys you added.

I understand that this is complex so if you are like me you want to look at code samples so I would suggest that you have a look at the LifetimeTests in our unit test project to get a set of examples of how all these work.

OK lets see if we can figure out Tonko's problem, but to do that I have a few questions.
1) How did you setup the container? Are you purely using a catalog or did you add the bar instance directly into the container via Compose? The reason I ask is bewcause if Bar was constructed via a catalog then I would expect the export you pass to ReleaseExport to implement IDisposable because you have it labeled as NonShared, however if it was constructed outside of MEF and added via Compose then ReleaseExport would do nothing and the NonShared declaration would be ignored. We only use the PartCreationPolicy if the container constructs the object.
2) Why are you manually constructing the ContractBasedImportDefinition to pass to GetExports? Why not just call GetExports<UserControl>("Bar")? 
3) When you did your GC.Collect test did you null out your bar reference before hand? If not the then the object would not be released, in fact you would need to hold a WeakReference to that object to even programatically determine if the object was released.

Hope this helps,
Wes 

 

Oct 2, 2010 at 10:31 PM
Edited Oct 4, 2010 at 7:45 PM

Thanks for weighing in Wes.

I should have probably given all the details up front. However, when several libraries are involved (MEF and another fine piece of software i.e: http://mefedmvvm.codeplex.com/) I tend to first try and get responses to narrowly targeted questions to both sides, and then use them to hopefully figure out the correct way to proceed.  After the initial post, debugging the root references seems to show that view is not collected because its view model reference is held by MEF, rather than MEF holding a direct reference to a view export. 

Here is WinDbg GCRoot command result on view model (of Bar view) instance address and then I will answer your questions:

> !gcroot -nostacks 07700740DOMAIN(0726C1C8):
HANDLE(Pinned):3a01dd4:Root:0867b910(System.Object[])->
07681f20(System.ComponentModel.Composition.Hosting.CompositionContainer)->
07682280(System.Collections.ObjectModel.ReadOnlyCollection`1[[System.ComponentModel.Composition.Hosting.ExportProvider, System.ComponentModel.Composition]])->
0768226c(System.Object[])->07681d60(MEFedMVVM.ViewModelLocator.MEFedMVVMExportProvider)->
07681d78(System.ComponentModel.Composition.Hosting.CatalogExportProvider)->
076823c0(System.ComponentModel.Composition.Hosting.ImportEngine)->
076823f8(Microsoft.Internal.Collections.ConditionalWeakTable`2[[System.ComponentModel.Composition.Primitives.ComposablePart, System.ComponentModel.Composition],[System.ComponentModel.Composition.Hosting.ImportEngine+PartManager, System.ComponentModel.Composition]])->
07682408(System.Collections.Generic.Dictionary`2[[System.Object, mscorlib],[System.ComponentModel.Composition.Hosting.ImportEngine+PartManager, System.ComponentModel.Composition]])->
076fe5f4(System.Collections.Generic.Dictionary`2+Entry[[System.Object, mscorlib],[System.ComponentModel.Composition.Hosting.ImportEngine+PartManager, System.ComponentModel.Composition]][])->
076fe560(System.ComponentModel.Composition.Hosting.ImportEngine+PartManager)->
076fe490(System.ComponentModel.Composition.ReflectionModel.DisposableReflectionComposablePart)->
07700740(Test.ViewModels.BarViewModel)

Since BarViewModel and Bar have references to each other, unless both are not reachable via some GC root, both will remain uncollected. When a view gets created, its VM or BarViewModel is implicitly imported by MEFedMVVM infrastructure because view XAML contains a special attached property. This property will make MEFedMVVM instantiate (non-Shared) BarViewModel via MEF and attach it to a view as its XAML DataContext.

Answers to questions:

1) I use DirectoryCatalog and never explicitly call Compose on the container. Since I am explicitly importing a view, which has said MEFedMVVM attached property to import BarViewModel, Compose may be happening in there.

2) The way I am getting exports is because I wanted to be able to see all the exports and their metadata that were found in DirectoryCatalog. Directory will contain a bunch of Dlls, all exporting a "Bar" contract on some WPF UserControl, but it is attributed with other metadata as well. So initially it was for debugging purposes. Once I enumerate through the exports I really need only one specific Bar view which has certain metadata value. The second reason for using this overload is that it returns IEnumerable of Exports, rather than Lazy<Bar> and I can call ReleaseExport on the former but not on the latter.

3) Bar view reference is assigned to a WPF Content property of ContentControl which is a singleton and alive as long as App is alive. IOW:

when I find the right view from GetExports IEnumerable  I do:

Bar bar = (from export in exports

               where export.Metadata[key] == ...

               select export).SingleOrDefault();

ContentControl.Content = bar.Value;

Release all exports;

GC.Collect();

I was considering nulling the Content property before overwriting it with a new Bar reference, moving GC.Collect() elsewhere, etc. But after a dozen of calls to the code as above, each call instantiating a different Bar view, all of them are still in memory, so I don't think it matters. I use Debug trace in finalizer to detect when view and view model get collected. It happens only when container is disposed.

So now the problem is understanding what causes MEF to hold a reference to a BarViewModel, as gcroot shows above. If it is due to something in MEFedMVVM implementation of ExportProvider, what should I look for before I contact the author of the library? 

I am hoping if BarViewModel would get collected, it is the only object holding a reference to a Bar view so Bar would then get collected as well.  

Thanks 

Oct 4, 2010 at 6:23 PM

MEF hold references in two situations

Parts marked with CreationPolicy.Shared

Parts marked with CreationPolicy.NonShared + part implements IDisposable.

Oct 4, 2010 at 7:52 PM
Edited Oct 4, 2010 at 7:53 PM

My model has references to unmanaged resources and these must be disposed. 

So, if I were to remove a standard IDisposable implementation and dispose unmanaged resources by calling some method named for ex. Clear(), all would be fine and I wouldn't have memory leaks?

I can do that but, unless I am totally misunderstanding the above, it does strike me as somehow wrong. Get rid of IDisposable in order to dispose :)

Oct 4, 2010 at 11:44 PM

I'm not sure I understand you. Did you check our documentation on lifetime? http://mef.codeplex.com/wikipage?title=Parts%20Lifetime&referringTitle=Guide

Oct 5, 2010 at 6:07 AM
Edited Oct 5, 2010 at 6:36 AM

Hi Haveriss,

Yes, and documentation says the same thing as you do:

<quote>

Thus, the container will not hold references to parts it creates unless one of the following is true:

  • The part is marked as Shared
  • The part implements IDisposable
  • One or more imports is configured to allow recomposition

</quote>

I am using strictly boolean logic:

a) I don't want the container to hold a reference to a part.

b) from what you wrote and from the quote above it follows that, if the part implements IDisposable, then the container will hold a reference to a part.

c) thus if I make my part *NOT* implement IDisposable, container won't hold a reference to that part.

d) I need to free unmanaged resources and to that end I made my part implement IDisposable. This is .NET standard pattern in my understanding and is not anything related to MEF. Many .NET programmers, if you tell them that you have unmanaged resources that need  be freed deterministically, would assume that you will implement IDisposable. Lots of blogs and Microsoft online documentation take it for granted too.

Thus I'll change my part so that it doesn't implement IDisposable and I'll free unmanaged resources without using IDisposable interface/pattern so that c) will be true and I will accomplish what I wanted to accomplish:

free unmanaged resources held by a part *AND* make container not hold a reference to a part so the part itself will get garbage collected.

Or have I really crossed into the Twilight Zone?

 

Developer
Oct 5, 2010 at 7:18 PM

While it is true that the container will hold references to Disposable NonShared parts the way to clean those up, assuming you don't want to dispose the container, is to call ReleaseExport. Which is where I think we started this thread at. If ReleaseExport is not doing what it isn't supposed to then there is a bug somewhere.

I'm not familar with MEFedMVVM but in your debug output above I see MEFedMVVMExportProvider, which could potentially cause the problem. Do if that export provider is wrapping the exports coming from the CatalogExportProvider? If it is then it could be the broken link.

.... I just had a quick look at MEFedMVVMExportProvider at http://mefedmvvm.codeplex.com/SourceControl/changeset/view/60612#1279042 and it is where we are loosing the information.

        protected override IEnumerable<Export> GetExportsCore(ImportDefinition definition, AtomicComposition atomicComposition)
        {
            var exports = _exportProvider.GetExports(definition, atomicComposition);
            return exports.Select(export => new Export(export.Definition, () => GetValue(export)));
        }

 If this guy is going to wrap the Export then it needs to have a disposable Export and call dispose on the export it is wrapping in order for the full disposal to work.

Oct 6, 2010 at 4:19 PM

Thanks for all the info about the issue, I still can't say that I know what it is for, but I'm gonna check out the test suit, that might help. Again, thanks for sharing!

Oct 6, 2010 at 7:35 PM
Edited Oct 6, 2010 at 7:36 PM

Same from me. I posted Wes response to the other library discussion list and pointed the author to this thread.

I expect/hope that adding IDisposable implementation to the other library exports and then calling ReleaseExport on them will be sufficient to get my View Model instances collected. That's what I think it is for.

Jan 13, 2011 at 11:23 PM
Edited Feb 15, 2011 at 11:00 PM

Can somebody point me to an example of the correct way to wrap disposable Export?

Is something like this intended ?:

public class ExportWrapper : Export, IDisposable
{
  Export _wrapped;

  public ExporWrapper(Export wrapped) : base(wrapped.Definition(), () => GetValue(wrapped))
  {
     _wrapped=wrapped;   
  }     

  public void Dispose()  
  {     
      IDsposable id = _wrapped as IDisposable;

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

      // what else to dispose ? 

  }
 
  protected override Object GetExportedValueCore() { return _wrapped.GetExportedValueCore(); }  // ??? needed

   ... other overrides ??? 
}

 

Thanks
Feb 15, 2011 at 11:01 PM

kick

May 8, 2012 at 7:34 AM

HI,

Have you found a solutions for this issue, if you have then please share with us, as i am facing the same issue as the multiple objects are being created for the Models and Views and are not being disposed until the application got closed.

May 9, 2012 at 12:06 AM

Nope,

Dealing effectively with (in my case) author(s) of three inter-dependent external libraries is pretty tough. Especially if people go on to other projects, are not on your boss' payroll or don't remember, have time, care, etc. to deal with the past issues.

I am just lucky that my users' usage pattern didn't make this problem become a big deal - the application can tolerate the resulting typical memory leaks.

Good luck! 

Sep 6, 2013 at 2:27 AM
Edited Sep 6, 2013 at 2:28 AM
I'm having the same issue. Anyone found any solutions for this? Seems like a pretty major flaw to me.