Does Eventing Causes Memory Leaks? Seems too...

Aug 25, 2008 at 8:32 PM

Hi there,

So I created an addin and host contracts similar to what is in the Event Sample and began processing data. I used the pipeline builder to help out with the plumbing and began testing. What I discovered was that the I**Adapter's seem to create memoryleaks by creating a circular reference or holding onto a reference so that the CG never collects the addins - even if I ensure all events are unsubscribed to and null the addin.

Here is a dump using the Event Sample on this site. Just after Console.ReadLine I added the following code to break and take a look at the pipeline using SOS.dll

 

 

 

 

            Console.ReadLine();

 

            addin.WorkProgress -= _workerProcess;

            addin = null;

 

            GC.Collect();

            GC.WaitForPendingFinalizers();

 

            int i = 0; //set breakpoint here

<!--EndFragment-->

 

 

 

Here is what I found...

 

!dumpheap -type System.AddIn.Pipeline

Address MT Size

01eb8ec4 676382dc 20

01ebca54 03d8609c 20

01ebf248 676382dc 20

01ec1c2c 03d8609c 20

01ed98c8 03d8609c 20

01ee28e8 676382dc 20

01eed588 676382dc 20

01eef830 676382dc 20

01ef1ad0 676382dc 20

01ef3d78 676382dc 20

01ef6100 676382dc 20

01ef8364 676382dc 20

01efa618 676382dc 20

01efccfc 676382dc 20

01efef70 676382dc 20

01f011d4 676382dc 20

total 16 objects

Statistics:

MT Count TotalSize Class Name

03d8609c 3 60 System.AddIn.Pipeline.ContractHandle

676382dc 13 260 System.AddIn.Pipeline.ContractHandle

Total 16 objects

//TAKE THE FIRST ONE AND LOOK INTO IT...

!gcroot 01eb8ec4

Note: Roots found on stacks may be false positives. Run "!help gcroot" for

more info.

Error during command: Warning. Extension is using a callback which Visual Studio does not implement.

Scan Thread 7948 OSTHread 1f0c

ESP:19ed30:Root:01eb8ce0(Samples.Events.HostSideAdapters.IAddInContractToViewHostAdapter)->

01eb8ec4(System.AddIn.Pipeline.ContractHandle)

ESP:19ed34:Root:01eb8ce0(Samples.Events.HostSideAdapters.IAddInContractToViewHostAdapter)->

01eb8ec4(System.AddIn.Pipeline.ContractHandle)

ESP:19ed38:Root:01dcb404(System.Collections.Generic.List`1[[Samples.Events.IAddIn, HostView]])->

01eb9ec4(System.Object[])->

01eb8ce0(Samples.Events.HostSideAdapters.IAddInContractToViewHostAdapter)

ESP:19ed54:Root:01dcb404(System.Collections.Generic.List`1[[Samples.Events.IAddIn, HostView]])->

01eb9ec4(System.Object[])

ESP:19ed7c:Root:01dcb404(System.Collections.Generic.List`1[[Samples.Events.IAddIn, HostView]])->

01eb9ec4(System.Object[])

Scan Thread 5548 OSTHread 15ac

Scan Thread 6640 OSTHread 19f0

Scan Thread 5192 OSTHread 1448

Scan Thread 6192 OSTHread 1830

Scan Thread 7256 OSTHread 1c58

DOMAIN(00330D08):HANDLE(WeakLn):2e10fc:Root:01e72cf4(System.Runtime.Remoting.Contexts.Context)->

01d41334(System.AppDomain)->

01e72b3c(System.Runtime.Remoting.DomainSpecificRemotingData)->

01ebb884(System.Runtime.Remoting.Lifetime.LeaseManager)->

01ebb8ac(System.Collections.Hashtable)->

01ebb8e4(System.Collections.Hashtable+bucket[])->

01ed8e08(System.Runtime.Remoting.Lifetime.Lease)->

01eb9370(Samples.Events.HostSideAdapters.IWorkProgressEventHandlerViewToContractHostAdapter)->

01eb8ce0(Samples.Events.HostSideAdapters.IAddInContractToViewHostAdapter)

DOMAIN(00330D08):HANDLE(WeakSh):2e12d8:Root:01eb8ce0(Samples.Events.HostSideAdapters.IAddInContractToViewHostAdapter)->

01eb9ec4(System.Object[])

DOMAIN(00330D08):HANDLE(WeakSh):2e12dc:Root:01eb8ce0(Samples.Events.HostSideAdapters.IAddInContractToViewHostAdapter)->

01eb9ec4(System.Object[])

DOMAIN(00330D08):HANDLE(Pinned):2e13fc:Root:02d41010(System.Object[])->

01e76964(System.Collections.Hashtable)->

01efc8b8(System.Collections.Hashtable+bucket[])->

01ed8060(System.Runtime.Remoting.ServerIdentity)->

01eb9370(Samples.Events.HostSideAdapters.IWorkProgressEventHandlerViewToContractHostAdapter)

DOMAIN(00330D08):HANDLE(Strong):2e15bc:Root:01ed8060(System.Runtime.Remoting.ServerIdentity)->

01eb9ec4(System.Object[])

//NOW LOOK AT THE OBJ JUST AFTER THE LEASE LINE...

!dumpobj 01eb9370

Name: Samples.Events.HostSideAdapters.IWorkProgressEventHandlerViewToContractHostAdapter

MethodTable: 00307594

EEClass: 00c9579c

Size: 48(0x30) bytes

(C:\Users\Tony.SUPEROFFICE_ASA\Downloads\Event_Sample\Event Sample\output\HostSideAdapters\HostSideAdapters.dll)

Fields:

MT Field Offset Type VT Attr Value Name

6f9d0508 400018a 4 System.Object 0 instance 01ed8060 __identity

00000000 4000145 8 0 instance 01eb93a0 m_lifetimeTokens

00000000 4000146 c 0 instance 00000000 m_contractIdentifiers

6f9d0508 4000147 10 System.Object 0 instance 01eb93b8 m_contractIdentifiersLock

6f9c8ea8 4000148 14 System.Random 0 instance 01eb93c4 m_random

6f9a43b8 4000149 20 System.Boolean 1 instance 1 m_registeredSponsor

6f9d0508 400014a 18 System.Object 0 instance 01eb94c4 m_registerSponsorLock

6f9a43b8 400014b 21 System.Boolean 1 instance 0 m_zeroReferencesLeft

6f9d2b38 400014c 1c System.Int32 1 instance 0 m_tokenOfAppdomainOwner

6f9d0508 4000011 24 System.Object 0 instance 01eb8ce0 _view

6f9cd1a8 4000012 28 ...ection.MethodInfo 0 instance 01eb8d78 _event

//NOW LOOK AT THE m_lifetimeTokens OBJ...

!dumpobj 01eb93a0

Name: System.Collections.Generic.List`1[[System.Int32, mscorlib]]

MethodTable: 6f9c09ec

EEClass: 6f786c3c

Size: 24(0x18) bytes

(C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)

Fields:

MT Field Offset Type VT Attr Value Name

6f9d2a88 40009d8 4 System.Int32[] 0 instance 01ed9c4c _items

6f9d2b38 40009d9 c System.Int32 1 instance 1 _size

6f9d2b38 40009da 10 System.Int32 1 instance 1 _version

6f9d0508 40009db 8 System.Object 0 instance 00000000 _syncRoot

6f9d2a88 40009dc 0 System.Int32[] 0 shared static _emptyArray

>> Domain:Value dynamic statics NYI

00330d08:NotInit dynamic statics NYI

003e03a8:NotInit <<

///

Notice how the size is 1. The reference count should be 0 so it can be collected! This may not be the only culprit, but it seems clear something is amiss.

Best regards.

Aug 27, 2008 at 9:04 AM
No one notice this?
Sep 15, 2008 at 2:14 AM
Hi Tonster,

I had a look at the sample, and to me it seems like it's doing the right thing.

When we instantiate an add-in in a remote domain or process, we register the add-in adapter as a sponsor for its own lease (ContractBase handles this for you). The default lease time is 5 minutes, so the add-in can stay alive for five minutes even after the host loses all references to it. Since the add-in holds a handle to an EventHandler adapter it stays alive until the lease expires as well. Once the lease has expired, all references should be cleaned up.

If you find that any references do stay alive after the lease has expired, please let me know.

Thanks,

-Mueez
Sep 18, 2008 at 7:16 PM
Hi Mueez,

Things are always great when they should happen, unfortunately things are generally not in your favor. This time is no exception.

I have read the documentation and know what is supposed to happen, but since you are so keen on hearing it over and over, let me say it again...

References still exist even after the lease (default) has expired. This is still true after ten minutes. This is still true even after verifiying all event subscriptions have been unsubscribed, and the addin is set to null, and after three times the lease time has passed.

Have you read your colleagues post about this problem? The PipelineBuilder does not seem to implement these recommended patterns.

http://blogs.msdn.com/zifengh/archive/2008/04/28/coding-patterns-to-avoid-in-addin-pipeline-development.aspx


Please try out the example before asking me to let you know a third time.

Best regards,

Tony
Mar 26, 2009 at 1:17 AM
I am also facing issues that Tonster321 described. It causes memory leaks. Is there any progress in implementing correct pattern into PipelineBuilder?

Regards,
Martin
May 30, 2009 at 8:52 PM

I've also experienced memory leaks when sending events across app domain boundaries. I haven't found a solution yet.

This article on AddIn lifetime management suggests that the problem might be related to objects passed by reference across app domain boundaries?

http://blogs.msdn.com/clraddins/archive/2007/07/18/add-in-lifetime-management-pete-sheill-jim-miller.aspx#comments

My guess is that an event created on the AddIn side gets passed to the host, increasing its reference count. But when the host handles the event, I'm not sure that the count ever gets decremented. And that means that the garbage collector can't ever free that memory. After enough events get thrown, the system runs out of memory.

I'm going to alter my contracts so that I only pass value types. Maybe that will help.

Jun 4, 2009 at 3:35 AM

Tonster321: Have you changed anything to make this work, in the source code, which I could integrate into the delta I just posted?

Jan 20, 2011 at 8:51 PM

Has anyone been able to reproduce this using the INativeHandleContract when pushing WPF elements across domains?  I believe I have this issue right now.  Let me explain my sample.  I have a basic contract with two methods (GetFrameworkElement and Dispose).  GetFrameworkElement is implemented by the addin to return a System.Windows.Controls.Button.  The AddinAdapter converts the FrameworkElement to an INativeHandleContract by using the FrameworkElementAdapters:

var frameworkElement = _view.GetFrameworkElement();
var inhc = FrameworkElementAdapters.ViewToContractAdapter(frameworkElement);
return inhc;

Then my host adapter performs the following to get back the FrameworkElement (actually an AddInHost object):

var inhc = _contract.GetFrameworkElement();
var fe = FrameworkElementAdapters.ContractToViewAdapter(inhc);
return fe;

I then host this button in a new WPF form on the host side.  I then attach with WinDbg and find out the !gcroot's.  I do notice that one of the chains has the Lease pointing to AddInHostSite pointing to AddInHost (which is the FrameworkElement on the host side):

Root:01fd80f4(System.Windows.Threading.Dispatcher)->
01fd1ad8(System.Threading.Thread)->
0206abf8(System.Runtime.Remoting.Contexts.Context)->
01fd12ec(System.AppDomain)->
0206aa5c(System.Runtime.Remoting.DomainSpecificRemotingData)->
0213d8a8(System.Runtime.Remoting.Lifetime.LeaseManager)->
0213d8d0(System.Collections.Hashtable)->
0213d908(System.Collections.Hashtable+bucket[])->
0213db80(System.Runtime.Remoting.Lifetime.Lease)->
0213d2dc(MS.Internal.Controls.AddInHostSite)->
0213c294(MS.Internal.Controls.AddInHost)

Then I close the window which is hosting the AddInHost object (my plugin's Button).  At this point my assumption is that the host side will garbage collect this object once the Host Side Lease Manager removes the Lease for AddInHost (could be five minutes).  So I Continue the app from WinDbg and wait and then Break into the app with WinDbg again later.  This time the !gcroot's show the following:

Root:01fd80f4(System.Windows.Threading.Dispatcher)->
01fd1ad8(System.Threading.Thread)->
0206abf8(System.Runtime.Remoting.Contexts.Context)->
01fd12ec(System.AppDomain)->
0206aa5c(System.Runtime.Remoting.DomainSpecificRemotingData)->
0213d8a8(System.Runtime.Remoting.Lifetime.LeaseManager)->
0213d8d0(System.Collections.Hashtable)->
0213d908(System.Collections.Hashtable+bucket[])->
0213d2dc(MS.Internal.Controls.AddInHostSite)->
0213c294(MS.Internal.Controls.AddInHost)

So... This is what remains.  The Lifetime Tokens are 1 instead of zero and the Lease is gone... but the AddInHost is still referenced...  I would have expected it to be removed.  A similar thing happens on the AddIn side with AddInHwndSourceWrapper which is what the INativeHandleContract really is.  This object persists as well in a similar manner.  I'm trying to understand why this is.  My sample is pretty bare bones and I have been trying different things and using WinDbg to see the results.  I have even tried to call HwndHost.Dispose() on the Host Side Button within the HostAdapter and this shows the object as disposed (through WinDbg).  But still referenced. So when does the AddInHost and / or the AddInHwndSourceWrapper really get released?  If ever?  And how do I ensure this happens and still work within the System.AddIn framework?

All this is to really troubleshoot a memory leak in our main application.  So I created this bare bones solution to limit the amount of variables.  

Jul 19, 2011 at 10:59 AM

Hi all,

After testing we have found a problem with the HwndSource / AddInHostSite references too. We created a very simple test application where the only thing we do is transfer the FrameworkElement from AddIn to Host. We do NOT use, in any way, the FrameworkElement in the host itself. So the reference is left for the Garbage collector. In this test we set the default Lease and RenewOnCallTime of remoting to 5 seconds. When we loop through this code a 100 times or so, we observed the following:

  1. Memory only increased and was not released. (We use System.GC.Collect and GC.WaitForPendingFinalizers after each call to get the UI)
  2. We noticed that when calling System.AddIn.Pipeline.FrameworkElementAdapters.ContractToViewAdapter(INativeHandleContract inhc) in the Host adapter, an AddInHostSite object was marshalled to the AddIn
  3. When we implemented INativeContractHandle ourselves, we did NOT see the marshalling of the AddInHostSite from Host --> AddIn
  4. The amount of AddInHwndSourceWrapper System.AddIn.Pipeline.AddInHwndSourceWrapper objects was the same as the amount of calls we made to get the FrameworkElement from the AddIn.
  5. The same goes for MS.Internal.Controls.AddInHost and AddInHostSite
  6. When we shut down the Dispatcher with Dispatcher.Current.InvokeShutdown, all references were removed.

This has led us to believe that a circular reference is created between the Host and AddIn. This reference keeps a lot of memory alive as the entire business model is attached to the Framework element at the AddIn side. We have created a very ugly workaraound that basically does the following:

  1. Intercept the Marshalling of the AddInHostSite object to the AddIn appdomain.
  2. Register this object to be disconnected (from the remoting services) and its lifetime revoked (Reflection on private parts of AddInHostSite . . . ugh!)
  3. Disconnect and revoke this object when the host form closes.

I know that this a rather tricky solution but it does clean up the references between Host and AddIn and we could verify that the system was pretty stable in memory consumption. In case anyone has found another/better solution, I would very much like to hear it.

Best regards,

John