Tuesday, March 17, 2009

On the importance of Garbage Collection

Update (21/10/2011):
It's been more then 2 years and it seems Infragistics have done nothing about this. They made a very idiotic design decision to sacrifice a fundamental behavior of managed code and then insist on not fixing it. Reader Jarrett posted an update that extends the fix below for win7 and Infragistics v10.3 and possibly 11.1 - I've added it to the code below.
Thanks Jarret!

---------

About two years ago we found a nice UI package for WinForms called NetAdvantage by infragistics.
A very nice pack, lots of useful controls and not too expensive. Maybe a bit slow, but this is not a problem in most applications.

For a while all was good and well, we built our application and our clients were happy.
Then one day - disaster!
Our application started crashing!
This was after a rather long development cycle where we added support for many new features in the hardware (this was a management application for specialized hardware).
Worse yet, we only started noticing it in QA. The reason was that it only happened after sending configuration to the hardware about 10-15 times without restarting the app - something we almost never do in dev.

Well, when your app crashes after repeated anything its very natural to suspect a memory leak. So i carefully watched memory consumption while calling send configuration. Memory consumption was increasing on task manager, but this is to be expected in managed environments (the simple explanation is that new memory is allocated as long as possible before old memory is reacquired. the actual explanation is not very simple :) ) . Since the machine had 2 gigs of memory and the app only grew from about 150 MB to 200 MB before crashing i figured it was something else.

What i did notice during my exhilarating time with Task Manager was that User Objects were growing rapidly. Being originally of the java persuasion i was unfamiliar with these User Objects. Turns out it was to do with windows handles and the silly way win32 api does UI.
It also turns out that windows has a limit of about 10,000 open handles. That seemed like a lot to me and i didn't understand how we could even approach this limit so fast. Sure, we have a lot of complex UI elements, but still - firefox was no.2 in the object count on my machine and only had 600 handles. Our app had~3,000 just after startup. This didn't seem likely.

The thing was handle leak and memory leak were very similar under managed environment. we used the excellent dot Tracer to locate the source of our leak. After a few hours staring at rather complicated object graphs it turned out that the leak came from...well...i guess my opening was a dead giveaway... - Infragistics Controls!

Anyone who is a developer in a managed environment is well aware of the crimes associated with keeping references to One's object when they have been removed from scope. It's very easy to do it by accident. a Hashmap that wasn't cleared correctly, an errant thread that keeps holding your objects or misunderstood relationships with event handler. All these and more were explored during my search for the leak. All these were not the case with Infragistics.

No - they did it on purpose.
Huh? you say. How, or why, would anyone do this on purpose?
The short answer is "i don't know". The longer answer is that they wanted a mechanism that will allow them to change the look and feel if the user changed the windows theme while the program was running. To do that they hooked into some theme change events and maintained links to every control. The short answer still holds tho , because its a really bad excuse. As i pointed out to Infragistics, you can easily use WeakReference to completely solve this problem.

During this ordeal i was talking to Mr. Vince McDonald, the manager of dev support at Infragistics. it took me a very long time and many lengthy emails and an almost religious debate until i finally got him to admit it was indeed a design flaw. He informed me that "..we will not be able to apply any changesfor it as part of a hotfix" as "..any such changes would have a very high chance to destabilize our current systems". he also promised that "..changes we may make can only safely be done as part of a volume release".
Well, this sounded reasonable to me so i decided to wait with this post. However 3 volumes have been released since my original conversation with him and the problem has not been fixed.

Despite a complete lack of support from Infragistics, we were finally able to resolve the issue ourselves. We had to use a rather nasty trick to bypass their mechanisms.
Basically we reflected their objects and forcefully cleaned all the references.
The code to resolve the problem is posted below.
Important note - This fix was prepared for v7.3 and may work with 10.3 (Thanks Jarrett) I dont know if it will work for any other version. I Suspect it might, and i would appreciate it if you tried it on a different version and it worked for you.


using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using Infragistics.Win;
using System.Reflection;
using Infragistics.Win.AppStyling;
using System.Diagnostics;
using Snoops.UI;
using System.Collections.Specialized;
using System.Collections;

namespace CleaningServices
{

///  IMOPORTANT NOTICE:
///  THIS HACK WORKS ONLY WITH Infragistics V7.3
///  AND POSSIBLY 10.3 (Thanks Jarrett)
///  IT WAS CREATED FOR THIS VERSION ONLY.
///  IF IT WORKS WITH ANY OTHER VERSION, ITS BY CHANCE ALONE.
///
///
/// <summary>
///  ReleaserControl is a special control designed to rid our code of the ugliness introduced by using the Infragistics UI Package.
///  Unfortunately someone in Infragistics made the "Design Descision" (according to their support personal) to turn all their UI objects into unmanaged object.
///  They acheived this by strong binding every control to static events held by static classes deep within the infrastructure.
///  As a result, if a UI class holds an infragistics control within it's Controls list, it will never get garbage collected.
///  Infragistics controls NEVER get garbage collected, unless Dispose is explicitly called on the control.(the Dispose method releases the even bind).
///
///  WTF??? you say, well, yes. since every control holds a strong reference to Parent, and the control itself is held
///  by a static event handler, your controls will not get collected when they get out of scope, if they hold an infragistics control.
///  this will naturally cause a memory leak. this in itself is not the worst of it. since all controls hold a Windows Handle,
///  your app will crash with the lovely "Cannot Create Window Handle" exception somewhere around 10,000 objects.
///
///  So, how did we solve this problem? well, we didnt REALLY solve it. we did a hack.
///  this seems to work for our current usage of the package, and i did not notice any unexpected behavior in the UI controls.
///  the limitation of this fix is that it was created for the Infragistics package v7.3 and might not work for any other version.
///  that's the way hacks go i'm afraid..
///
///  What does our hack hack do? well, it forces unregistration of infragistics controls from all the static places we could find them binding to.
///  calling the InfragisticsCleaner.ClearEventBinding(); causes all events for all controls to be unregistered, allowing them to get GCed as they should.
///  the price we pay is that some windows events will not be handled this way,(like changing themes) , but we are willing to live with that.
///  There might be other implications..we dont know.
///
///  for ease of use we added the ReleaserControl that extends UserControl. all it does is add an event handler
///  which calls the InfragisticsCleaner.ClearEventBinding(); method whenever a control is added.
///  extend this class in your user control to allow your controls to get GCed again.
///
///  a once in a while thread calling the ClearEventBinding method would probably work just as well, but it might not get called
///  exactly when you need it...
///
/// </summary>

public class ReleaserControl : UserControl
{
public ReleaserControl() : base()
{
this.ControlAdded += new ControlEventHandler(ReleaserControl_ControlAdded);
}
void ReleaserControl_ControlAdded(object sender, ControlEventArgs e)
{
InfragisticsCleaner.ClearEventBinding();
}
};
public class InfragisticsCleaner
{
private static StaticPropertyHolder[] properyHolders = new StaticPropertyHolder[]
{
new StaticPropertyHolder(typeof(StyleManager), "styleChangedDelegate", "StyleChanged"),
new StaticPropertyHolder(typeof(Office2007ColorTable), "colorSchemeChanged", "ColorSchemeChanged"),
new StaticPropertyHolder(typeof(Office2007ColorSchemeChangedNotifier), "colorSchemeChanged", "ColorSchemeChanged"),
new StaticPropertyHolder(typeof(RoleSelectionUI),"queryComponentRoleDelegate","QueryComponentRole"),
new StaticPropertyHolder(typeof(XPThemes), "themeChangedDelegate", "ThemeChanged") 
};

public static void ClearEventBinding()
{
foreach (StaticPropertyHolder holder in properyHolders)
holder.ClearEvents();
AcessibleTextManagerCleaner.CleanDictionaries();
}

class AcessibleTextManagerCleaner
{
ListDictionary[] dictionaries;
static AcessibleTextManagerCleaner cleaner;
//initialize the static cleaner instance.
static AcessibleTextManagerCleaner()
{
String[] propertNames = new String[]{ "SubclassList", "ControlList", "EditorList" };
cleaner = new AcessibleTextManagerCleaner(GetAccesibleTextManagerType(), propertNames);
}
public static void CleanDictionaries()
{
foreach (ListDictionary dictionary in cleaner.dictionaries)
dictionary.Clear();
}
private AcessibleTextManagerCleaner(Type accessibleTextManagerType, String[] dictionaryPropertyNames)
{
//retrieve the singleton instance.
PropertyInfo instanceField = accessibleTextManagerType.GetProperty("Instance", BindingFlags.NonPublic | BindingFlags.Static);
object instance = instanceField.GetValue(null, new Object[0]);
//get the properties
dictionaries = new ListDictionary[dictionaryPropertyNames.Length];
for (int i = 0; i < dictionaryPropertyNames.Length; i++)
dictionaries[i] = ReflectListDictionary(dictionaryPropertyNames[i], instance, accessibleTextManagerType);
}
//get the dictionaries to clear.
private static ListDictionary ReflectListDictionary(String propertyName, Object instance,Type type)
{
PropertyInfo instanceField = type.GetProperty(propertyName, BindingFlags.NonPublic | BindingFlags.Instance);
ListDictionary list = (ListDictionary)instanceField.GetValue(instance, new Object[0]);
return list;
}

private static Type GetAccesibleTextManagerType()
{
//we need the infragistics assembly that holds our type (its a private type so we cant access it directly)
Assembly infragisticsAssm = Assembly.GetAssembly(typeof(EditorWithMask));
Type[] types = infragisticsAssm.GetTypes();
Type accessibleTextManagerType = null;
//search through the assembly until we get what we want.
foreach (Type t in types)
{
if (t.Name.IndexOf("AccessibleTextManager") != -1)
{
accessibleTextManagerType = t;
break;
}
}
return accessibleTextManagerType;
}

};
class StaticPropertyHolder
{
EventInfo eventInfo;
FieldInfo field;

public StaticPropertyHolder(Type type, String delegateFieldName, String eventName)
{
this.field = type.GetField(delegateFieldName, BindingFlags.Static | BindingFlags.NonPublic);
eventInfo = type.GetEvent(eventName);

}
public void ClearEvents()
{
Delegate eventDelegate = field.GetValue(null) as Delegate;
if (eventDelegate != null)
{
Delegate[] invocationList = eventDelegate.GetInvocationList();
foreach (Delegate del in invocationList)
{
eventInfo.RemoveEventHandler(null, del);
}
}

}

};


};
}


64 comments:

david said...

Wow. First, thanks for your efforts & the post. Secondly, that really sucks. What a poor design choice. Imagine all those apps out there like buckets with holes in them.

I'm also using v7.3 and your code worked for me.

Yossi Naar said...

:)
Glad to have helped.
And yes, it is a poor design indeed.
I suspect that there are many Infragistics users out there that aren not even aware of the problem.
It took some time to even suspect that the cause was 3rd party UI controls.
Whats worse is that some users probably accept Infragistics "Solution", which is to manually call "Dispose" on each control before it goes out of scope.

Sanjeev Sai said...

Grea....t job done.
But this is the work around for Infrgistics controls for Windows.

I will appreciate if you could suggest me work arround for infragistics controls for asp.net. I am also facing same problem for my webapplication. In case of web ScriptManger class of Infragistics.WebUI.shared.util namespace is culprit and not releasing page.

bhoomi said...

I am also stuck with memory leak.
I am using Infragistics 9.1 Grid. I tried calling "InfragisticsCleaner.ClearEventBinding()" in Dispose() method. Does not throw any error.
I am using Red Gate's Ants Memory Profiler to detect the leak. I am seeing 2 big dictionaries, on drilling down i found that DropDown's are not releasing these dictionaries. Tried lots of things. Plz help. Or from where can i get ver 7.3 then also i would change my Infragistics.

Plz help.

Yossi Naar said...

Hi Sanjeev, i am afraid i cannot help you with the asp memory cleaning issues, as we have only used the winforms version of infragistics.
also, The cause for the asp issue is probably very different then the one in winforms, as they do not use the same global bindings as winforms (there's no theme change events to tie into in asp).

As asp debugging is more difficult then the winforms, i would suggest trying to distill the problem into the most minimal page that doesnt release and try a profiler.

Yossi Naar said...

Hi bhoomi,

It is possible that infragistics added addtional bindings in later versions. i heard from someone working with vol 8.1 that the fix doesn't work there either.

regarding an earlier version of infragistics, i would suggest that infragistics support might be able to give you an earlier version. each volume release requires a different license key, so you'd have to ask them.

finding the source of the leak is unfortunately difficult. now that you know which dictionaries you are looking at, try to find who holds them, who is the static member that is the root of all evil.

you'd probably have to use a decompiler to look at the usage of these dictionaries, otherwise it's almost impossible to determine the true root.

regarding the DropDowns, are they statically holding the dictionaries? what is preventing GC?

bhoomi said...

Hi yossi,
Thanks for reply.
I am able to see the call stack that is reposonsible for binding the datatable to UltraDropDown of UltraGrid.
Before UltraGrid's dispose i tried setting all UltraDropDown's DataSource = null. Then disposed all the UltraDropDowns attached to grid. Then i included your code after this. But it still shows me that 2 large dictionaries are still present in memory and they were created for UltraDropDown of UltraGrid.
I thought setting them to null would help. How do i set those static dictionaries to null?

Thanks again.

Yossi Naar said...

bhoomi, i have to tell you i dont fully understand what you mean.
perhaps a code sample showing what exactly you are doing would be more helpful.

I can tell you that in my experience, if you explicitly call Dispose on the infragistics objects they usually cleanup after themselves, so its strange that you have these dictionaries left over.

on the other hand, i dont fully understand who is held by who (UltraDropDown by the dictionary, or vica versa), or what is the role of the dictionaries you mentioned.

Yossi.

Sanjeev Sai said...

Yussi, Thanks for your reply.

using Infragistics controls is like hell. Moreover developer support is worst. we are in the situation where release of application is round the corner and QA is not giving green signal since applications crashed when five concurrent users are using application.

any we are trying hard to fix it. we'll let you know if fixed.

Anonymous said...

You'll find a similar issue in the spell checker control - had to use reflection to remove reference from an internally held static colection.

bhoomi said...

I am facing this problem with Infragistics 2.1, 5.3 and 9.1 trial. I will create a small demo and post tomorrow for explaining my problem better.

Thanks,
Bhoomi.

Sanjeev Sai said...

Hi Anonymous,

Thanks for quick reply,

I tried to release it through reflection also. But no luck so far. In case of UltraWebGrid, IsInAsyncPostBack( Systems.Web.UI.Page) method is not releasing the page. and this is private static method.

Anonymous said...

So even calling the dispose method on the IG object before leaving the scope will not suffice? Why not?

Sanjeev Sai said...

I think thats because if a asp.net page contains the IG object(UltraWebGrid) the whole page is passed as a parameter to the methods of ScriptManager Class ( Init & IsInAsyncPostBack Methods) of Infragistics.WebUI.Shared.Util.

and Page is not released. I tried to achiev it through reflection and set the parameter(page) to null and then disposed but no luck so far.

Yossi Naar said...

Hi Sanjeev (and anonymous),

Are you sure that calling Dispose on the UltraWebGrid object does not release the ties? this is different then my original issue - this constitutes a bug that will require a HotFix from infragistics.

Calling Dispose on the page might not release the control.

Yossi.

bhoomi said...

Sorry guys I am replying so late. Got busy with other issues. This issue is very important though.

My Code Sample :
There are 2 Forms:
1. frmStartup. Holds a button and onClick event follwing code is written :
Form1 objForm = new Form1();
objForm.ShowDialog();
objForm = null;

2. Form1. Holds a button called Load, Infragistics UltraWinGrid 9.1 trial and Infragistics UltraCombo 9.1 trial.
On load button click following is the code :
string sConn = "server=.\\sql2005;database=navnit;uid=sa;pwd=pass";
SqlConnection conn = new SqlConnection(sConn);
SqlCommand scom = new SqlCommand("select '' as [sAccountIDFK], 1
select top 4000 sAccountID,sAccountName from bmaccount ", conn);
SqlDataAdapter sda = new SqlDataAdapter(scom);
DataSet dsObject = new DataSet();
sda.Fill(dsObject);
this.ultraGrid1.DataSource = dsObject.Tables[0];
this.ultraCombo1.SetDataBinding(dsObject.Tables[1], null);
this.ultraCombo1.ValueMember = "sAccountID";
this.ultraCombo1.DisplayMember = "sAccountName";
conn.Dispose();
scom.Dispose();
sda.Dispose();
dsObject.Dispose();

Overridden Dispose method as follows :
protected override void Dispose(bool disposing)
{
//dsObject = null;
ultraCombo1.Dispose();
ultraGrid1.Dispose();
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}

In this i am manually disposing both the controls.

I could not attach the memory profiler's screen-shot else I would have done that.

I have tried multiple things.
One form just has Infragistics UltraGrid no comboBox's and there is no memory leak.

Another form has only Infragistics ComboBox and again no memory leak.

When I combined both of them there is a memory leak.
By forcing GC also it does not remove the objects from memory.

Have pointed this out to Infragistics support they said they are working on it.

Any help would be highly appreciated.


Thanks,
Bhoomi.

Yossi Naar said...

Hi Bhoomi,

You say you manually dispose the controls, but you don't show any code calling dispose.

overriding the dispose the way you did does not activate the method.
Dispose is only called on destruct if your form goes out of scope. since infragistics are probably holding your form hostage, it will never get the call to dispose.(unless there's some other call you didn't show).

Hope this helps.

also setting objForm=null as you did is meaningless. you are only setting the local reference to null. the form is still alive as long as it's "shown". even if you call close on the form it will only get disposed if it either goes out of scope, or someone calls Dispose. (i.e you can listen to Form.OnClosing and call your dispose on those objects when it's called).

Thanks,
Yossi.

bhoomi said...

Hi Yossi,

A million thanks...

I have now called Dispose(true) from Form1_Closing() event and yes you were right it disposed off everything.

I thought Dispose would be called automatically from the Windows's Form's base class somewhere. I never checked that.

Thanks a lot.
Now I will apply this to our application code and if I face any trouble, would trouble you again.

Thanks,
Bhoomi.

Yossi Naar said...

:)

Glad to have helped.

You were right to assume Dispose should be called. this is the main issue with infragistics.
By holding a static reference to your form (because Controls on a form bind to their parent) they prevent Dispose from being called by the finalizer during GC, and force you to Dispose manually.


Yossi.

bhoomi said...

Hi Yossi,

Again stuck up with a small thing that is not letting garbage collector take my objects.
It is MDI window now!
If I keep open my form from MDI it releases memory but when I set form's MDIParent then it does not release memory.

I googled it. Found that I should use singleton pattern in that case I tried out following thing.

Form objcilUltraGridForm = new frmCilUltragridForm();
objcilUltraGridForm.MdiParent = this;
objcilUltraGridForm.FormClosed += new FormClosedEventHandler
(delegate(object sender1, FormClosedEventArgs e1)
{
objcilUltraGridForm = (Form) this.SingleInstanceForms["Id"];
this.SingleInstanceForms.Remove("Id");
objcilUltraGridForm.MdiParent = null;
objcilUltraGridForm.Dispose();

});
this.SingleInstanceForms.Add("Id", objcilUltraGridForm);
objcilUltraGridForm.Show();

SingleInstanceForms's declaration is as follows :

private Dictionary string, Form SingleInstanceForms;

I tried to dispose the object again in this delegate but it does not remove it.
Memory remains the same.

Plz help.


Thanks,
Bhoomi.

bhoomi said...

I again did my trick and created another simple form with only a dropdown in MDI and it was releasing memory. Then I found out few events were there in my application which when were put in MDI were not releasing memory. So the problem is sorted out.

Thanks,
Bhoomi.

Saiprasad said...

Sanjeev Sai,

Where you able to resolve the IG webgrid mememory leake issue.

bhoomi said...

If you are asking me, yes I was able to resolve memory leak issue in IG WinGrid not WebGrid. Yossi had suggested to call dispose() on each grid manually before closing the form.

Thanks,
Bhoomi.

M.i.S. Nemesis said...

Yossi, do you have the infragistics support ticket number so I can reference from?

Yossi Naar said...

Hi Nemesis,
i am afraid not, it was i think two years ago now since the ticket was submitted.

There's an open discussion thread on the badly indexed infagistics forums.

i am curious tho - what for?

jasunb said...

i can't believe this bug- was notified by an important customer that our app was slowing down after many hours of use. after spending the afternoon with a memory profiler, i found the same issue as you described two years ago. The result is a huge memory leak since forms' data cannot be collected due to outstanding references. it's madness. Solid work on your part, though. godspeed.

InfragisticsUser said...

haven't read all this thru yet, but I just wanted to say that Infragistics are indeed a very strange company.

Their components look good with plenty of options and events, but they are surprisingly stubborn about NOT FIXING inherent problems in their package. One more example other then your is their Right-to-Left (lack of) support. They were asked to do it at least since 2003, but keep saying "not in this volume".

It's a shame. Had I known they take such "unsmart" decisions, I would never have used them in the first place.

It's too late for me now. Oh well..

Yossi Naar said...

Yes, i agree.
Infragistics is very strange.
It is by an unfortunate coincidence that i find myself working with their controls again in a different project and for a different company.

Other than their apparent inability to repair bugs or add fundamental features, their documentation is lacking to non-existent and its somewhere between hard to impossible to find good code samples and examples for using their controls.

InfragisticsUser said...

Yossi,

If I understand correctly, calling form.Dispose() prevents this Infragistic's leak from happening, and only when Dispose isn't called, we are left with event references from Infragistic's static objects (preventing GC from activating the finalizer that calls form.Dispose), right?

But form.Close() makes a call to its Dispose(), and normaly a visual form is closed in one way or another, and so its Dispose() method will be called after all, and no leak will take place, meaning that this is kind of a rare problem.

Am I missing something here?

Yossi Naar said...

InfragisticsUser,

This is either a very critical or very minor issue depending on you use case.

If you only create static forms and close them when you're done - you might be fine.
As long as all the controls on the form are in the Controls collection their Dispose method will be invoked by the call to their parent.

However, if your forms hold dynamic elements that can go out of scope or some other entity holds dynamic elements they and all that use them will not be GCed.


It also holds that if you manually call dispose on those objects everything will be fine.
But this is not the correct behavior for objects in a managed environment with a powerful garbage collection mechanism.

There are some very rare elements in the framework that require special handling when you dispose of them, and UI controls are not normally included - and for good reason.

The IDisposable interface exists so that controls can release their unmanaged resources. By design Dispose is invoked by the finalizer of a control.
If UI controls cannot be GCed, the finalizer will never be called and they will hold their resources forever.

While it is *possible* to manually manage this mechanism, it is not the desired way, and there are many pitfalls in attempting to do so.

InfragisticsUser said...

Thanks for your detailed reply Yossi,

I am still not sure about this, since Infragistic's controls *are* normally on the form's objects list (or on the objects list of one of its sub controls that is in the form's objects list).

And so when the form is closed, its Dispose method is called, calling its Infragistic's controls Dispose method as well, which release the problematic events pointers. At this point, the form should have no references pointing to it (at least not because of the Infragistic problem), and the GC should release it, and all the dynamic objects that might be associated with it should be released as well.

What do you think?

Yossi Naar said...

InfragisticsUser

It depends on what you mean by "Dynamic Objects". My definition is objects that are created during the form's lifetime, and dynamically added/removed from the form's Controls list when needed.

Lets say the user drags and image control into some panel, and then deletes it.

The application has no use for the image object, and so it simply lets it get out of scope by calling Controls.Remove(myImage) (no reason to Dispose it..), expecting it to be GCed.
If there is some static reference to this object it will not be collected - and you have a memory leak.

This is further complicated by event binding.
Lets say my form binds to a mouse event on the image i mentioned before, and then my image get's out of scope. Now its not just my image that will never be gced - its also my form, as registering with a delegate means holding a reference.
Of course, there are other ways objects may be referenced, i am mentioning event binding because people tend to forget it means holding a reference and because they are used very often in winforms.

InfragisticsUser said...

Yossi,

I guess the image object in your example is an Infragistic object.

And yes, if you remove it from the form's Controls list before its Dispose method is called, then the image's Dispose method won't be called and we will have the Infragistics-leak for that image and everything it is referring to (including the form itself as you mentioned, in case the form is listening to its events).

Actually if you attach custom control to Infragistics Editor's Right/Left-Buttons, this custom control won't be on the Editor's controls list, and so its dispose method won't be automatically called when the form is closed, thus if it is an Infragistics based control, we will get a memory leak again.

So I guess the Infragistic-leak is indeed a significant problem after all..

Thank you for the interesting and useful information..

Jason Beres (Infragistics) said...

Hi Everyone,

I am happy to put this issue to bes and let everyone know that Infragistics made a change which deals with controls that are instantiated at runtime and aren't registered in the forms control hierarchy. This is part of our 10.1 code branch. I would also like to clarify that this issue occurs when you have controls which are not part of a parent control and you don’t dispose them before releasing your references to them. I am sure you would all agree this is bad practice in C# and shouldn’t be done. Either way, you can call Dispose() on any controls which are not part of a Form’s control hierarchy to resolve the issue today.

If you would like to talk about this or anything else Infragistics, feel free to email me at jasonb@infragistics.com. And again, we have implemented code to ensure this memory is returned to the pool in the 10.1 release.

Yossi Naar said...

Well joan, no - i do not agree its a bad practice.
Bad practice is to implement a system that causes instances to be left outside the scope of the user and cannot be garbage collected.

I don't to go into this argument with yet another representative of Infragistics.

If you are interested in the many arguments for WHY leaving strongly referenced, non-garbage collectible UI objects out of the reach of user code, you can refer to your Vince McDonald with whom i had a very long discussion that ended with him agreeing that this was a deep design flaw.

I would only point out that no Microsoft based control (or any other commercial or open source control) that i know of suffers from this affliction, and while they all implement IDispoable,they also implement a finalizer that calls Dispose.

If you are still not convinced, I'd be happy to give a lecture on the internals of the GC mechanism, the usage of finalizes, weak references and unmanaged resources in c#.

Btw: i am happy to hear this issue was finally resolved.
I wonder if the people who paid for your product some years ago will get a fix?

serhio said...

Thank you for your effort.

We also has memory leak problems problems With Infragistics controls.

As we wasn't sure that the problem comes from Infragistics, I created a little test project

I started with a new WinForm projet with 2 forms (Form1 - MDI, and Form2 that is opened with a click on a Form1.Button1). On the second form I put some Infragistic controls.

I supposed that after opening the Form2 and closing it, it will be Disposed, but not non GC-collected.

However, started the memory profiler and opening multiple times Form2 ((new Form2()).Show()) (clicking on the MDI Form1.Button1) didn't detect any memory leaks from the Form2 object.

When I did form2.ShowDialog(); (instead of just Show();) I saw that the form2 where not GC-collected.

After that I set in the Form2_ControlAdded() your ClearEventBinding

Re-profiled the application and... form2 successfully cleaned!

Our Infragistics version is 6.3, so < 7.3 I think this because it worked.

Thank you!

Ragna said...

Guy,

Thank you so much!

Your post have save us from a nice memory leak in our application :)

You have won a subscriber!

Yossi Naar said...

:)

Ragna and serhio
You are both most welcome.

Its amazing that YEARS later the problem still exists for many users.

Miguel Angel said...

Hi,

I'm experiencing the same problem, but instead of using infragistics 7.3 I'm using version 8.3. I tried your solution but as you expected, it does not work with this version.

I'm wondering if you had played with this version.

Thanks.

Yossi Naar said...

Hi Miguel,

Unfortunately the answer is no - we never experimented with a different version - didn't want to risk our fix breaking again.

I would expect that the problem would still be somewhere in that area tho.

In the meantime - we moved to WPF and Telerik's controls.

Good Luck,
Yossi.

Miguel Angel said...

Well,

Thanks Yossi, I appreciate your cooperation, maybe I'll try infragistics v7.3.

As a comment, i asked infragistic team for support and they told me that they are working on this bug and they are planning to include a fix on this year. I hope it works.

F Quednau said...
This comment has been removed by the author.
F Quednau said...

I was just about to write pretty much the code you posted, so thank you so much. I don't think the design decision is very clever on Infragistics' part, but at least there's still reflection to rectify decisions of 3rd party manufacturers.

btw, we are using INfragistics 8.2. I can only find attachment to the "colorSchemeChanged" event, so I'll only uset he code related to that event, buta s you can see, the problem persists. I can't see why WeakReferences aren't used. Have they ever said why not?

Yossi Naar said...

It's good to know that the workaound works for later versions, i got some questions about that in the past but i never tested it on later revisions.

They never did say why - you can see the comment by Joan from infragistics saying they fixed the issue - i never tested v10.1 and i will never use their controls again so i don't know if the issue has really been resolved.

I am still getting about 100-150 hits a month on this page, many from the infragistics forums so i can only assume this is still an issue for many infragistics users.

என்.விநாயகமுருகன் navina14@hotmail.com said...

Hi
I am also facing the same problem in my project.. Let me check this solution and i may troubled you in case if any

Anonymous said...

There are other leak issues with Infragistics...

After having invested so much time programing my application...only to find out the thing leaks like a sieve is totally disheartening...

Try this little trick out...

Drop a toolbar manager on a form...and an image list...

Put a half dozen buttons on a toolbar and assign them images...

Now hover over the tool bar buttons and each time they go hot then not hot the memory meter just starts rolling up...

This isn't even about disposing of controls...but simply hovering over tool bar buttons...

Want another one?

Put a dock manager on your form...again add an image list...

Add some docked panes and infragistics tree controls in a tab group...assign an image from your image list to the tab appearance...

Now click between the tabs...leak leak leak leak...And god forbid you assign different or even the same image to the hot tab, selected tab etc...it just magnifies the leak..

Now click on your infragistics trees...more leaking...every time you bring focus to the tree it leaks...

I am devastated as I am programming for non commercial use and cannot afford to upgrade...

Totally sucks

Carson Wales

Yossi Naar said...

Hi Carson,

It is very unfortunate indeed.
I was expecting Infragistics to offer a free upgrade/fixed version, but alas, they only seem to care about you for 12 months after your last payment.

I have since moved on to work with WPF, which is a far superior technology.
There are many open UI projects under WPF and customizing controls is far easier and better under WPF.

The rich customization options allow us to reach designs we could never have dreamed of under winforms.

It has been my experience that closed source third party packages should be avoided as much as possible.

Infragistics most of all.

Anonymous said...

Hi Yossi,

Great article and thanks for sharing.

My application using the latest version 2010.3 is also suffering memory leaks.

I'm new to hunting for leaks.

Can you share what is the best way to trace/track the leaks? Although using Task Manager will show the application memory consumption keeps increasing, it doesn't narrow down to the individual components? Also, how did you track the windows handles being consumed?

Thanks in advance,

Yossi Naar said...

:)

You're most welcome.

Task manager is really your friend here.
There are various techniques to track memory leaks, but specifically for the handle leak we just used task manager.
If you go into the task manager "select columns" option you'll see that it has "User Objects".
As i recall the user objects represents the open window handles for the applications.

This value is limited to somewhere between 5000 to 10000 elements by the operating system, and when they run out all hell breaks loose.

What we did was generate a lot of objects that took many handles.
Make sure they are IN VIEW - otherwise they don't get a handle.
then we destroyed the owning scope and ran a forced GC a few times.

If the handles stick you have a very easy to see leak.
Then it's the slow and painful process of finding the individual component (or in our case - the whole Infragistics package) that causes the leak.

For proper memory leaks it's best to use a memory profiler.
like the Jetbrains memory profiler.
It let's you freeze the current memory state and the exact count of individual objects.

You can often find the "root" of the problem by looking for whichever object is keeping a reference to your objects and preventing it from GC.

Very useful.


Hope this helps,
Yossi.

Anonymous said...

Thanks Yossi for sharing some techniques in tracing leaks.

As you've suggested, we've just bought Jetbrains .Trace Memory profiler to help in identifying classes/objects in different memory state. Being new to the tool, I must admit on first encounter with the report it produced, it's overwhelming given our application is complex. However I've kind of expected that's the case given the power of .Trace. Just have to start staring and dissecting the report and hopefully it will reveal the culprit(s).

Do you know of any good tips and techniques on using .Trace memory profiler? I don't seem to be able to find any decent tutorial on it.

Cheers,
Bianco

Yossi Naar said...

Hi Bianco,

I am glad you found the Memory Profiler useful.

I don't know of any tutorials myself, but i will try to give you some useful tips.

The primary uses of a memory profiler are:

1) Finding memory leaks
2) Reducing memory footprint
3) Improving performance (reducing GC time)

In each of these scenarios you are looking for different information from your profiles.

To find memory leaks, you want to get some idea of which objects are never collected, and who is holding them.

To reduce memory footprint, you want to know which objects take up most of you memory.

To reduce GC and improve performance, you want to know which objects are created and destroyed i n large quantities.

I will focus on the first case - finding a memory leak, as this is the most common and most difficult case.

A memory leak under a managed environment is defined by having objects in memory that we though should be collected.
The ONLY way this can happen is when someone, somehow is holding a reference to our objects.

To debug the leak you must first be able to consistently reproduce it, and ideal to have some set of operations that keep increasing the memory consumption until the inevitable crash.

You need to take a snapshot for the initial state, do the series of steps needed to increase memory usage and take another snapshot.

What you are trying to find are new objects that were added, and in particular any objects that should not have been accumulated in memory.

After you found which objects are leaking, you want to find out who is holding them.

This is a much harder process.
The best thing is to start from the code and look for any static dictionaries, events, and any and all collections that might be holding your objects, and try to figure out why they are still alive.

If you can't figure it out by looking at the code, you can start going over objects that MAY be holding your reference, and see if they might still be holding a reference to your objects.

The profiler gives you (right click on object) two sets of objects you can look at.
One is the Reachable object list - all the objects that can be reached in some way from a given object.
and the other is the Held object list - the list of objects that are directly or indirectly from a given object.

The objects in the Reachable list (unless they are somewhere held by a weak reference) are objects that CANNOT be garbage collected until the observed objects is collected.

The objects in the Held list are the objects directly held by your object, they also cannot be garbage collected.

The reachable list is where you might find objects that you think should have been GCed but are really somehow held by your object.

If you find something held by your object that you think should not have been held, then you have the relatively simple task of finding out why they are still there.

If they are somewhere in the reachable list, then you have the much harder task of finding out which of the reachable object is really the one holding on to your objects.

I recommend using the profiler to figure out WHICH objects are being held and not WHO is holding on to them.
The first question can be answered in a relatively strait-forward way by looking at which object counts are skyrocketing when they shouldn't be.

The second one usually requires looking through the code and looking for the usual suspects : collections,dictionaries, static references, events and delegates.

Let me know if you managed to find your leak.

Yossi.

Anonymous said...

Hi Yossi, I tried the class but I do not notice if functioning properly, and no change in the application memory, add to an existing form ClearEventBinding the call (), My code example is:

Form test
------------

public FormTest()
{
this.ControlAdded += new System.Windows.Forms.ControlEventHandler(this.FormTest_ControlAdded);
InitializeComponent();
}

private void FormTest_ControlAdded(object sender, ControlEventArgs e)
{
InfragisticsCleaner.ClearEventBinding();
}

----------------------

Ingragistics am using 6.3 and the application that is running is going to 200 mb in minutes.


Know if the version 2010v2 solves this problem?

You can send me a sample code of how implementers ClearEventBinding class, thank you very much.

Yossi Naar said...

Hi Anonymous poster.

I have no idea if the code will work for v6.3 (but i suspect it would) OR if the newer version still has this issue.

I am not sure if your test would reflect changes to the memory with/without the call to the releaser because i have no idea how many controls you are trying to add, and later remove.
if you dont remove anything - it will have no effect.

If you remove a small amount of entities then the change may be too small to measure (you'd also need to call GC.Collect(0)).

Glenn said...

Yossi,

Can you share more informaiton about how ReleaserControl is defined in your XAML markup? perhaps a small example of your XAML? I'm modifying your code for version 9.1.xx. Memory management still sucks for the Infragistic objects. No reply from support - I wonder why?

I have UserControl.Resources defined in my XAML and he complains when I try to use ReleaserControl as it does not have a Resource property.

Thanks,
Glenn

Anonymous said...

Quick update on this:

Infragistics claims to have fixed it in their 2010v1 version.

http://blogs.infragistics.com/forums/t/8451.aspx?PageIndex=2

Jarrett said...

Very interesting blog post. I have been having similar issues with v10.3 winform controls on Windows 7.

The original fix was for v7 controls, but with a small addition it can be used to clean up 10.3 controls.

private static StaticPropertyHolder[] properyHolders = new StaticPropertyHolder[]
{
new StaticPropertyHolder(typeof(StyleManager), "styleChangedDelegate", "StyleChanged"),
new StaticPropertyHolder(typeof(Office2007ColorTable), "colorSchemeChanged", "ColorSchemeChanged"),
new StaticPropertyHolder(typeof(Office2007ColorSchemeChangedNotifier), "colorSchemeChanged", "ColorSchemeChanged"),
new StaticPropertyHolder(typeof(RoleSelectionUI),"queryComponentRoleDelegate","QueryComponentRole"),
new StaticPropertyHolder(typeof(XPThemes), "themeChangedDelegate", "ThemeChanged")
};

Windows 7 and 10.3 attach to an expanded and set of static events, so all that was needed was to add them in and the fix worked for 10.3

Really this has saved a large project from failing, so many thanks for a great post. Hopefully someone will find the 10.3 (and I am guessing 11.1) fix useful.

Yossi Naar said...

Thanks Jarrett.

Glad to have helped.

I've updated the post with your changes.
Hopefully it will help other get out of their Infragistics mess.

Hemal Shah said...

Such a great post.....

I am really frustrated with this events of infragistic, I saw it in my ants profiler but does not know, how to release.

Thanks to you, that you have resolved my issue

Jasmeet said...

Hi Yossi,
Thanks to you and Jarret for the fix.
We are using 10.3 so I am really hoping this will fix our issue.
My question is that We are using the Infragistics controls on Windows form. We do not have UserControls.
How do I extend the ReleaserControl in order to use your code?

Yossi Naar said...

Hi Jasmeet,

If you're using a small number of forms, you can just use the regular dispose when you close them (on the close event).

In any case you can change the base class to be form or anything you like - it should still work.


Yossi.

Ahmet said...

Hello Yossi Naar,
I want to use your solution in my application to solve memory leak. (For Infragistic 10.3) But I could't add it my project. I tried to use these class istead of UserControl class. It creates events, every time, but the problem is I have more than ten class which uses Usercontrol and so if try to change all of them to your class. Memory usage increase rapidly (I think beacuse of adding too much event). My project is nearly done so I couldn't find exactly point of memory leak. Is it correct implementation, using your class instead of Usercontrol(one or more class)?

Yossi Naar said...

Hi Ahmet,

The class itself only adds a reference to monitor control added events so it can run the release code (detach the controls from the static holders) - it's pretty much negligable.

Since the class is directly inherited from UserControl it doesn't really change any of the semantics in the program.

Alternatively you can run a thread to periodically detach all the roots - but that's much harder and potentially more performance intensive.

I'd recommend just replacing your user controls with the releaser and seeing if that works.

Matthew Cosgriff said...

Hello Yossi Naar,

I am using Infragistics 11.1 and trying your solution with out code but in the StaticPropertyHolder::ClearEvents() call eventDelegate is always null.

Using sos.dll in the Visual Studio Immediate window after I open and close Forms which contain UltraWinGrid.UltraGrid I then get a dump of the heap and do some comparisons and see a bunch of Infragistics objects increasing in number even after GC has been forced.

Has the issue which your code addressed been fixed in 11.1? Has the set of static events changed for version 11.1?

Unfortunately for now I am limited to using sos in Visual Studio, no memory profiler or Decompiler.

Any thoughts or ideas would be appreciated.

waqas said...

Hi,

I am using 11.2 infragistic version, this fix doesn't seem to be working for this version.
Am facing same issue as you were. Kindly please let me know how can I fix this in 11.2 version

thanks