.NET Forum / .NET Framework / CLR / January 2006
Memory Leak in DesignSurface
|
|
Thread rating:  |
Bele din Carpati - 09 Dec 2005 19:30 GMT Seems that DesignSurface has a memory leak. Where should I report it? This can be observed by running the program bellow and push several times the <ShowDialog> and <CallGC> buttons and observe that the heap memory increase each time <ShowDialog> button is pushed. The leak can be also observed by running the CLR Profiler for Whidbey. Details from CLR Profiler are at the end of the message. CLRProfiler for Whidbey final can be downloaded from
http://www.microsoft.com/downloads/details.aspx?familyid=a362781c-3870-43be-8926 -862b40aa0cd0
I would like to know the cause and also get an workaround. (please)
Here the source-code //---------------------- BEGIN ----------------- using System; using System.Collections.Generic; using System.Windows.Forms; using System.Drawing; using System.ComponentModel.Design;
namespace TestMemLeakDesignSurface { static class Program { /// <summary> /// The main entry point for the application. /// </summary> [STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new MainForm()); } }
class MainForm : Form { public MainForm() { this.Text = "MainForm TestMemLeakDesignSurface";
Button btnShowDialog = new Button(); this.Width = 500; btnShowDialog.Text = "Show Dialog"; btnShowDialog.Location= new Point(20, 20);
Button btnCallGC = new Button(); btnCallGC.Text = "Call GC"; btnCallGC.Location= new Point(200, 20);
btnShowDialog.Click += new EventHandler(btnShowDialog_Click); btnCallGC.Click += new EventHandler(btnCallGC_Click);
this.Controls.Add(btnCallGC); this.Controls.Add(btnShowDialog); }
void btnShowDialog_Click(object sender, EventArgs e) { using (DesignSufaceHostForm form = new DesignSufaceHostForm()) { form.ShowDialog(this); } }
void btnCallGC_Click(object sender, EventArgs e) { // do whatever is possible to force GC to collect // nobody refer the DesignSufaceHostForm or Designed Form System.GC.Collect(); System.GC.WaitForPendingFinalizers(); System.GC.Collect(); System.GC.WaitForPendingFinalizers(); System.GC.Collect(); System.GC.WaitForPendingFinalizers(); long totalMemeory = System.GC.GetTotalMemory(true); MessageBox.Show(string.Format("totalMemory: {0:n}", totalMemeory)); } }
public class DesignedForm : Form { public DesignedForm() { this.Size = new Size(300, 300); this.Text = "Designed Form"; } }
class DesignSufaceHostForm : Form { DesignSurface designSurface; public DesignSufaceHostForm() { this.Size = new Size(500, 500); this.Text = "Host of DesignSuface"; designSurface = new DesignSurface(); designSurface.BeginLoad(typeof(DesignedForm)); Control view = designSurface.View as Control; if (view != null) { view.Dock = DockStyle.Fill; this.Controls.Add(view); } } } } //---------------------- END -----------------
This is extracted from CLR Profiler after aplying filter: "TestMemLeakDesignSurface.DesignedForm" and enable only Show Callers/Referencing Objects
<root> : 4.8 kB (100.00%) Handle, Pinning <root>->Handle, Pinning->(System.Object []): 4.8 kB (100.00%) Handle <root>->Handle->(System.Threading.ThreadAbortException): 0.0 bytes (0.00%) Handle <root>->Handle->(System.ExecutionEngineException): 0.0 bytes (0.00%) Handle <root>->Handle->(TestMemLeakDesignSurface.MainForm): 0.0 bytes (0.00%) Handle <root>->Handle->(System.SharedStatics): 0.0 bytes (0.00%) Handle <root>->Handle->(System.StackOverflowException): 0.0 bytes (0.00%) Handle <root>->Handle->(System.AppDomain): 0.0 bytes (0.00%) Handle, Pinning <root>->Handle, Pinning->(System.Object): 0.0 bytes (0.00%) Handle <root>->Handle->(System.OutOfMemoryException): 0.0 bytes (0.00%) Handle <root>->Handle->(System.Threading.Thread): 0.0 bytes (0.00%) Stack <root>->Stack->(System.Windows.Forms.ApplicationContext): 0.0 bytes (0.00%) Finalizer <root>->Finalizer->(System.Windows.Forms.Internal.DeviceContext): 0.0 bytes (0.00%) Stack <root>->Stack->(System.Windows.Forms.NativeMethods.MSG []): 0.0 bytes (0.00%) Stack <root>->Stack->(System.Windows.Forms.Application.ComponentManager): 0.0 bytes (0.00%) Stack <root>->Stack->(System.Windows.Forms.Application.ThreadContext): 0.0 bytes (0.00%) Handle <root>->Handle->(System.Reflection.Assembly): 0.0 bytes (0.00%) Handle <root>->Handle->(System.Security.PermissionSet): 0.0 bytes (0.00%) Handle <root>->Handle->(System.Reflection.Module): 0.0 bytes (0.00%) Finalizer <root>->Finalizer->(System.Drawing.BufferedGraphics): 0.0 bytes (0.00%) Finalizer <root>->Finalizer->(System.Windows.Forms.Internal.WindowsFont): 0.0 bytes (0.00%) System.Object [] Handle, Pinning->System.Object []->(System.Diagnostics.TraceSwitch,System.Internal.HandleCollector.HandleType [],System.Runtime.InteropServices.HandleRef,...): 4.8 kB (100.00%) (1 object, 4.0 kB (84.14%)) System.Collections.Hashtable System.Object []->System.Collections.Hashtable->(System.Collections.Hashtable.bucket []): 772 bytes (15.86%) (1 object, 56 bytes (1.15%)) System.Collections.Hashtable.bucket [] System.Collections.Hashtable->System.Collections.Hashtable.bucket []->(System.EventHandler): 716 bytes (14.71%) (1 object, 288 bytes (5.92%)) System.EventHandler System.Collections.Hashtable.bucket []->System.EventHandler->(System.Windows.Forms.Design.ControlCommandSet): 428 bytes (8.79%) (1 object, 32 bytes (0.66%)) System.Windows.Forms.Design.ControlCommandSet System.EventHandler->System.Windows.Forms.Design.ControlCommandSet->(System.ComponentModel.Design.DesignerHost.Site,System.Windows.Forms.Design.EventHandlerService,TestMemLeakDesignSurface.DesignedForm,...): 396 bytes (8.13%) (1 object, 76 bytes (1.56%)) TestMemLeakDesignSurface.DesignedForm System.Windows.Forms.Design.ControlCommandSet->TestMemLeakDesignSurface.DesignedForm->(System.ComponentModel.Design.DesignerHost.Site,System.String,System.Windows.Forms.Control.ControlNativeWindow,...): 320 bytes (6.57%) (1 object, 320 bytes (6.57%)) <bottom> : 0.0 bytes (0.00%)
Willy Denoyette [MVP] - 10 Dec 2005 00:11 GMT > Seems that DesignSurface has a memory leak. Where should I report it? > This can be observed by running the program bellow and push several times [quoted text clipped - 106 lines] > } > //---------------------- END ----------------- You have to dispose the DesignSurface when your Host container closes.For instance by adding ...
protected override void OnFormClosed(FormClosedEventArgs e) { designSurface.Dispose(); base.OnClosed(e); }
to your DesignSufaceHostForm class.
Willy.
Bele din Carpati - 10 Dec 2005 01:01 GMT I already tested that but didn't worked. I put the Dispose call in protected override void Dispose(bool disposing) and also in OnClose as you suggested but didn't worked.
So I still think this is a bug. Can you plese test your solutions next time you are posting?
>> Seems that DesignSurface has a memory leak. Where should I report it? >> This can be observed by running the program bellow and push several times [quoted text clipped - 119 lines] > > Willy. Willy Denoyette [MVP] - 10 Dec 2005 17:23 GMT >I already tested that but didn't worked. I put the Dispose call in > protected override void Dispose(bool disposing) and also in OnClose as you > suggested but didn't worked. > > So I still think this is a bug. > Can you plese test your solutions next time you are posting? Using your sample I noticed an (obvious) GDI and User handle leak (and as result a small memory leak), with the Dispose call in place the handles are retuned to their previous values when the designerhost gets closed. I further tested your sample and noticed that a number of desiner related objects are reaching (and staying) the gen2 heap for each instance of the designer. I will further investigate which objects and who keeps them rooted. Notice that the handle leak (not disposing the desiner form) is more of an issue, you will reach the maximum number of GDI handles limit before you exhaust available memory.
Willy.
Bele din Carpati - 11 Dec 2005 00:50 GMT The sample I posted was obviously not a production sample, but a sample to prove the memory leak.
In my real problem I already implemented
protected override void Dispose(bool disposing)
but this didn't solve my problem.
However I assure you that the dispose is called from whithin the form finalizer if the Form is not reacheable. But unfortunatelly seems that the form is reachable throu its Site an a EventHandler.
However the purpose of the post was to report a bug an get it fixed in next .NET 2.0 pach and hope for a workaround not to have a polemic with you.
What I also want to mention is that I reported bug on: MSDN Product Feedback Center
For details see link bellow:
http://lab.msdn.microsoft.com/productfeedback/viewfeedback.aspx?feedbackid=8131e f87-3227-49a0-925c-67a2ade8ef2b
Cristi
>>I already tested that but didn't worked. I put the Dispose call in >> protected override void Dispose(bool disposing) and also in OnClose as [quoted text clipped - 15 lines] > > Willy. Bele din Carpati - 11 Dec 2005 19:03 GMT I worked hard with Reflector and CLR Profiler to and I found out that some developer wrongly made a field static. Is about static field commandStatusHash from internal class System.Windows.Forms.Design.CommandSet+CommandSetItem. The workaround is to remove some of its items during Dispose (items that are related to DesignSurface that is disposed).
My opinion is that is issue is indeed a bug and what I'm doing here is only a workaround. My opinion is that it should be fixed with next service pack.
I forgot to mention that there still are 16 bytes that are leaking at each step, but this is significantly less than previous and I can live with it.
See the workaround bellow:
using System; using System.Collections.Generic; using System.Windows.Forms; using System.Drawing; using System.ComponentModel.Design; using System.Windows.Forms.Design; using System.Reflection; using System.Collections;
namespace TestMemLeakDesignSurface { static class Program { /// <summary> /// The main entry point for the application. /// </summary> [STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new MainForm()); } }
class MainForm : Form { public MainForm() { this.Text = "MainForm TestMemLeakDesignSurface";
Button btnShowDialog = new Button(); this.Width = 500; btnShowDialog.Text = "Show Dialog"; btnShowDialog.Location= new Point(20, 20);
Button btnCallGC = new Button(); btnCallGC.Text = "Call GC"; btnCallGC.Location= new Point(200, 20);
btnShowDialog.Click += new EventHandler(btnShowDialog_Click); btnCallGC.Click += new EventHandler(btnCallGC_Click);
this.Controls.Add(btnCallGC); this.Controls.Add(btnShowDialog); }
void btnShowDialog_Click(object sender, EventArgs e) { using (DesignSufaceHostForm form = new DesignSufaceHostForm()) { form.ShowDialog(this); } }
void btnCallGC_Click(object sender, EventArgs e) { // do whatever is possible to force GC to collect // nobody refer the DesignSufaceHostForm or Designed Form System.GC.Collect(); System.GC.WaitForPendingFinalizers(); System.GC.Collect(); System.GC.WaitForPendingFinalizers(); System.GC.Collect(); System.GC.WaitForPendingFinalizers(); long totalMemeory = System.GC.GetTotalMemory(true); MessageBox.Show(string.Format("totalMemory: {0:n}", totalMemeory)); } }
public class DesignedForm : Form { public DesignedForm() { this.Size = new Size(300, 300); this.Text = "Designed Form"; } }
class DesignSufaceHostForm : Form { DesignSurface designSurface; public DesignSufaceHostForm() { this.Size = new Size(500, 500); this.Text = "Host of DesignSuface"; designSurface = new DesignSurface(); designSurface.BeginLoad(typeof(DesignedForm)); Control view = designSurface.View as Control; if (view != null) { view.Dock = DockStyle.Fill; this.Controls.Add(view); } } protected override void Dispose(bool disposing) {
if (disposing) { ClearControlCommandSetWorkaround clearHelper = new ClearControlCommandSetWorkaround(designSurface);
designSurface.Dispose();
clearHelper.DoClear(); base.Dispose(disposing); } } }
/// <summary> /// This class is present as workaround for a bug /// http://lab.msdn.microsoft.com/productfeedback/viewfeedback.aspx?feedbackid=8131e f87-3227-49a0-925c-67a2ade8ef2b /// /// Because Microsoft chose to hide a lot of classes we are forced to heavely use the /// reflection to accomplish this. /// </summary> internal class ClearControlCommandSetWorkaround { object commandSet;
internal ClearControlCommandSetWorkaround(DesignSurface designSurface) { IDesignerHost host = designSurface.GetService(typeof(IDesignerHost)) as IDesignerHost; IDesigner designer = host.GetDesigner(host.RootComponent); DocumentDesigner docDesigner = designer as DocumentDesigner; commandSet = GetFieldValue(docDesigner, "commandSet"); } internal void DoClear() { // I think that commandStatusHash should not be static // now we have to clear the hash for Event handler references // that have as target the current commandSet Type commandSetItemType = commandSet.GetType().Assembly.GetType("System.Windows.Forms.Design.CommandSet+CommandSetItem"); Hashtable commandStatusHash = GetFieldValue(null, "commandStatusHash", commandSetItemType) as Hashtable; List<EventHandler> ehToRemove = new List<EventHandler>(); foreach (EventHandler eh in commandStatusHash.Keys) { if(eh.Target == commandSet) { ehToRemove.Add(eh); } } foreach (EventHandler eh in ehToRemove) { commandStatusHash.Remove(eh); } /* // the source-code comented was a previous workaround // seems that it is not needed anymore.
SetFieldValue(commandSet, "baseControl", null);
Type[] cmdSetTypes = new Type[] { commandSet.GetType(), commandSet.GetType().BaseType }; foreach (Type cmdSetType in cmdSetTypes) { Array cmdSetItemsArray = GetFieldValue(commandSet, "commandSet", cmdSetType) as Array; foreach (object cmdSetItem in cmdSetItemsArray) { SetFieldValue(cmdSetItem, "statusHandler", null); } SetFieldValue(commandSet, "commandSet", null, cmdSetType); } */ } #region Reflection related helpers static FieldInfo GetFieldInfo(object obj, string fieldName, Type objType) { FieldInfo fieldInfo = objType.GetField(fieldName, BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy); if (fieldInfo != null) return fieldInfo;
if (objType == typeof(object)) return null;
return GetFieldInfo(obj, fieldName, objType.BaseType); } static FieldInfo GetFieldInfo(object obj, string fieldName) { return GetFieldInfo(obj, fieldName, obj.GetType()); } static object GetFieldValue(object obj, string fieldName) { return GetFieldValue(obj, fieldName, obj.GetType()); } static object GetFieldValue(object obj, string fieldName, Type type) { FieldInfo fieldInfo = GetFieldInfo(obj, fieldName, type);
if (fieldInfo == null) { return null; } return fieldInfo.GetValue(obj); } static void SetFieldValue(object obj, string fieldName, object fieldValue, Type type) { FieldInfo fieldInfo = GetFieldInfo(obj, fieldName, type); if (fieldInfo == null) { return; } fieldInfo.SetValue(obj, fieldValue); } static void SetFieldValue(object obj, string fieldName, object fieldValue) { SetFieldValue(obj, fieldName, fieldValue, obj.GetType()); } #endregion } }
PS. For Willy From your part (as MVP) I expected a more clear response and maybe a workaround like the one I found. Can you see if I'm right and my fix don't have side effects that may broke something else?
>> Seems that DesignSurface has a memory leak. Where should I report it? >> This can be observed by running the program bellow and push several times [quoted text clipped - 119 lines] > > Willy. Willy Denoyette [MVP] - 12 Dec 2005 17:27 GMT >I worked hard with Reflector and CLR Profiler to and I found out that some >developer wrongly made a field static. Is about static field [quoted text clipped - 246 lines] > Can you see if I'm right and my fix don't have side effects that may broke > something else? I'm not affiliated to nor do I have any relations with MSFT other than being nominated as MVP. Note that being MVP doesn't mean I have any obligations as to respond to this or any other postings in these and other NG's. That said, I would suggest you wait for an reply to the issue you filed at the feedback site, only the developers owning the code can answer this question and it's up to them to decide whether it's a bug, a user error or by design. Anyway your "workarround" is not the right solution, you are not the owner of the code, so changing the implementation of internals by reflecting on private fields is by principle "dangerous" and should be avoided. That's exactly the reason why private they exists, to hide the implementation details.
Willy.
Bob Powell [MVP] - 31 Dec 2005 11:51 GMT Hi Willy, Apart from the fact that you are of course correct I found the OP's solution to be rather creative. It shows how the power of reflection can be used in some pretty scary ways. This goes against the grain of everything object oriented but does indeed solve the problem, partially anyway, of an annoying bug typical of the kind MS loves to leave in their code.
Personally I think that this kind of workaround is a great solution because it's very unlikely that this bug will be adressed in any near-future service packs due to the sheer inertia of MS's internal development prioritization. I can list a few equally serious bugs that hung around for years, the whole lifetime of the 1.1 release in fact, despite many bug reports and demands for service packs.
 Signature Bob Powell [MVP] Visual C#, System.Drawing
Ramuseco Limited .NET consulting http://www.ramuseco.com
Find great Windows Forms articles in Windows Forms Tips and Tricks http://www.bobpowell.net/tipstricks.htm
Answer those GDI+ questions with the GDI+ FAQ http://www.bobpowell.net/faqmain.htm
All new articles provide code in C# and VB.NET. Subscribe to the RSS feeds provided and never miss a new article.
>>I worked hard with Reflector and CLR Profiler to and I found out that some >>developer wrongly made a field static. Is about static field [quoted text clipped - 262 lines] > > Willy. Bele din Carpati - 01 Jan 2006 13:33 GMT Hi Bob
Do you know any official or unoficial list of known bugs for Whidbey?
Cristi
> Hi Willy, Apart from the fact that you are of course correct I found the > OP's solution to be rather creative. It shows how the power of reflection [quoted text clipped - 278 lines] >> >> Willy. Bob Powell [MVP] - 01 Jan 2006 22:49 GMT I don't know of an official bug list but the google search "+whidbey +bugs" turns up a few goood sites.
 Signature Bob Powell [MVP] Visual C#, System.Drawing
Ramuseco Limited .NET consulting http://www.ramuseco.com
Find great Windows Forms articles in Windows Forms Tips and Tricks http://www.bobpowell.net/tipstricks.htm
Answer those GDI+ questions with the GDI+ FAQ http://www.bobpowell.net/faqmain.htm
All new articles provide code in C# and VB.NET. Subscribe to the RSS feeds provided and never miss a new article.
> Hi Bob > [quoted text clipped - 286 lines] >>> >>> Willy.
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|