Home | Contact Us | FAQ | Search & Site Map | Link to Us
Sign In | Join | Other 45 Sites in Network
HomeAnnouncementsFree MagazinesWhite PapersSubmit Content
Discussion GroupsASP.NETWindows FormsLanguages.NET FrameworkVisual Studio.NET
Articles.NET FrameworkASP.NETToolsWindows Forms
.NET DirectoryOpen Source ProjectsUser GroupsWeb Resources
Related Topics
Visual Basic 6SQL ServerMS AccessOther DB ProductsMS Server ProductsMore Topics ...

.NET Forum / .NET Framework / CLR / January 2006

Tip: Looking for answers? Try searching our database.

Memory Leak in DesignSurface

Thread view: 
Enable EMail Alerts  Start New Thread
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.

Rate this thread:







Free Magazines

Get 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 ...

Oracle MagazineNetwork ComputingComputer WorldBio-IT WorldeWeekInformation WeekInfosecurity
 
Sign In
Join
My Latest Posts
My Monitored Threads
My Blog
My Photo Gallery
My Profile
My Homepage

Start New Thread
Enable EMail Alerts
Rate this Thread



©2008 Advenet LLC   Privacy Policy - Terms of Use
This website includes both content owned or controlled by Advenet as well as content owned or controlled by third parties.