The Labs \ Source Viewer \ SSCLI \ System.Internal \ DebugHandleTracker

  1. //------------------------------------------------------------------------------
  2. // <copyright file="DebugHandleTracker.cs" company="Microsoft">
  3. //
  4. // Copyright (c) 2006 Microsoft Corporation. All rights reserved.
  5. //
  6. // The use and distribution terms for this software are contained in the file
  7. // named license.txt, which can be found in the root of this distribution.
  8. // By using this software in any fashion, you are agreeing to be bound by the
  9. // terms of this license.
  10. //
  11. // You must not remove this notice, or any other, from this software.
  12. //
  13. // </copyright>
  14. //------------------------------------------------------------------------------
  15. /*
  16. */
  17. namespace System.Internal
  18. {
  19.    
  20.     using Microsoft.Win32;
  21.     using System;
  22.     using System.ComponentModel;
  23.     using System.Diagnostics;
  24.     using System.Diagnostics.CodeAnalysis;
  25.     using System.Runtime.InteropServices;
  26.    
  27.     using Hashtable = System.Collections.Hashtable;
  28.    
  29.     /// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker"]/*' />
  30.     /// <devdoc>
  31.     /// The job of this class is to collect and track handle usage in
  32.     /// windows forms. Ideally, a developer should never have to call dispose() on
  33.     /// any windows forms object. The problem in making this happen is in objects that
  34.     /// are very small to the VM garbage collector, but take up huge amounts
  35.     /// of resources to the system. A good example of this is a Win32 region
  36.     /// handle. To the VM, a Region object is a small six ubyte object, so there
  37.     /// isn't much need to garbage collect it anytime soon. To Win32, however,
  38.     /// a region handle consumes expensive USER and GDI resources. Ideally we
  39.     /// would like to be able to mark an object as "expensive" so it uses a different
  40.     /// garbage collection algorithm. In absence of that, we use the HandleCollector class, which
  41.     /// runs a daemon thread to garbage collect when handle usage goes up.
  42.     /// </devdoc>
  43.     /// <internalonly/>
  44.     internal class DebugHandleTracker
  45.     {
  46.        
  47.         //#if DEBUG
  48.         private static Hashtable handleTypes = new Hashtable();
  49.         private static DebugHandleTracker tracker;
  50.        
  51.         static DebugHandleTracker()
  52.         {
  53.             tracker = new DebugHandleTracker();
  54.            
  55.             if (CompModSwitches.HandleLeak.Level > TraceLevel.Off || CompModSwitches.TraceCollect.Enabled) {
  56.                 System.Internal.HandleCollector.HandleAdded += new System.Internal.HandleChangeEventHandler(tracker.OnHandleAdd);
  57.                 System.Internal.HandleCollector.HandleRemoved += new System.Internal.HandleChangeEventHandler(tracker.OnHandleRemove);
  58.             }
  59.         }
  60.        
  61.         private DebugHandleTracker()
  62.         {
  63.         }
  64.        
  65.         private static object internalSyncObject = new object();
  66.        
  67.         /// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker.IgnoreCurrentHandlesAsLeaks"]/*' />
  68.         /// <devdoc>
  69.         /// All handles available at this time will be not be considered as leaks
  70.         /// when CheckLeaks is called to report leaks.
  71.         /// </devdoc>
  72. /** @conditional(DEBUG) */       
  73.         public static void IgnoreCurrentHandlesAsLeaks()
  74.         {
  75.             lock (internalSyncObject) {
  76.                 if (CompModSwitches.HandleLeak.Level >= TraceLevel.Warning) {
  77.                     HandleType[] types = new HandleType[handleTypes.Values.Count];
  78.                     handleTypes.Values.CopyTo(types, 0);
  79.                    
  80.                     for (int i = 0; i < types.Length; i++) {
  81.                         if (types[i] != null) {
  82.                             types[i].IgnoreCurrentHandlesAsLeaks();
  83.                         }
  84.                     }
  85.                 }
  86.             }
  87.         }
  88.        
  89.         /// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker.CheckLeaks"]/*' />
  90.         /// <devdoc>
  91.         /// Called at shutdown to check for handles that are currently allocated.
  92.         /// Normally, there should be none. This will print a list of all
  93.         /// handle leaks.
  94.         /// </devdoc>
  95. /** @conditional(DEBUG) */       
  96.         [SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")]
  97.         public static void CheckLeaks()
  98.         {
  99.             lock (internalSyncObject) {
  100.                 if (CompModSwitches.HandleLeak.Level >= TraceLevel.Warning) {
  101.                     GC.Collect();
  102.                     GC.WaitForPendingFinalizers();
  103.                     HandleType[] types = new HandleType[handleTypes.Values.Count];
  104.                     handleTypes.Values.CopyTo(types, 0);
  105.                    
  106.                     Debug.WriteLine("------------Begin--CheckLeaks--------------------");
  107.                     for (int i = 0; i < types.Length; i++) {
  108.                         if (types[i] != null) {
  109.                             types[i].CheckLeaks();
  110.                         }
  111.                     }
  112.                     Debug.WriteLine("-------------End--CheckLeaks---------------------");
  113.                 }
  114.             }
  115.         }
  116.        
  117.         /// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker.Initialize"]/*' />
  118.         /// <devdoc>
  119.         /// Ensures leak detection has been initialized.
  120.         /// </devdoc>
  121. /** @conditional(DEBUG) */       
  122.         public static void Initialize()
  123.         {
  124.             // Calling this method forces the class to be loaded, thus running the
  125.             // static constructor which does all the work.
  126.         }
  127.        
  128.         /// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker.OnHandleAdd"]/*' />
  129.         /// <devdoc>
  130.         /// Called by the Win32 handle collector when a new handle is created.
  131.         /// </devdoc>
  132. /** @conditional(DEBUG) */       
  133.         private void OnHandleAdd(string handleName, IntPtr handle, int handleCount)
  134.         {
  135.             HandleType type = (HandleType)handleTypes[handleName];
  136.             if (type == null) {
  137.                 type = new HandleType(handleName);
  138.                 handleTypes[handleName] = type;
  139.             }
  140.             type.Add(handle);
  141.         }
  142.        
  143.         /// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker.OnHandleRemove"]/*' />
  144.         /// <devdoc>
  145.         /// Called by the Win32 handle collector when a new handle is created.
  146.         /// </devdoc>
  147. /** @conditional(DEBUG) */       
  148.         private void OnHandleRemove(string handleName, IntPtr handle, int HandleCount)
  149.         {
  150.             HandleType type = (HandleType)handleTypes[handleName];
  151.            
  152.             bool removed = false;
  153.             if (type != null) {
  154.                 removed = type.Remove(handle);
  155.             }
  156.            
  157.             if (!removed) {
  158.                 if (CompModSwitches.HandleLeak.Level >= TraceLevel.Error) {
  159.                     // It seems to me we shouldn't call HandleCollector.Remove more than once
  160.                     // for a given handle, but we do just that for HWND's (NativeWindow.DestroyWindow
  161.                     // and Control.WmNCDestroy).
  162.                     Debug.WriteLine("*************************************************");
  163.                     Debug.WriteLine("While removing, couldn't find handle: " + Convert.ToString((int)handle, 16));
  164.                     Debug.WriteLine("Handle Type : " + handleName);
  165.                     Debug.WriteLine(Environment.StackTrace);
  166.                     Debug.WriteLine("-------------------------------------------------");
  167.                 }
  168.             }
  169.         }
  170.        
  171.         /// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker.HandleType"]/*' />
  172.         /// <devdoc>
  173.         /// Represents a specific type of handle.
  174.         /// </devdoc>
  175.         private class HandleType
  176.         {
  177.             public readonly string name;
  178.            
  179.             private int handleCount;
  180.             private HandleEntry[] buckets;
  181.            
  182.             private const int BUCKETS = 10;
  183.            
  184.             /// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker.HandleType.HandleType"]/*' />
  185.             /// <devdoc>
  186.             /// Creates a new handle type.
  187.             /// </devdoc>
  188.             public HandleType(string name)
  189.             {
  190.                 this.name = name;
  191.                 this.buckets = new HandleEntry[BUCKETS];
  192.             }
  193.            
  194.             /// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker.HandleType.Add"]/*' />
  195.             /// <devdoc>
  196.             /// Adds a handle to this handle type for monitoring.
  197.             /// </devdoc>
  198.             public void Add(IntPtr handle)
  199.             {
  200.                 lock (this) {
  201.                     int hash = ComputeHash(handle);
  202.                     if (CompModSwitches.HandleLeak.Level >= TraceLevel.Info) {
  203.                         Debug.WriteLine("-------------------------------------------------");
  204.                         Debug.WriteLine("Handle Allocating: " + Convert.ToString((int)handle, 16));
  205.                         Debug.WriteLine("Handle Type : " + name);
  206.                         if (CompModSwitches.HandleLeak.Level >= TraceLevel.Verbose)
  207.                             Debug.WriteLine(Environment.StackTrace);
  208.                     }
  209.                    
  210.                     HandleEntry entry = buckets[hash];
  211.                     while (entry != null) {
  212.                         Debug.Assert(entry.handle != handle, "Duplicate handle of type " + name);
  213.                         entry = entry.next;
  214.                     }
  215.                    
  216.                     buckets[hash] = new HandleEntry(buckets[hash], handle);
  217.                    
  218.                     handleCount++;
  219.                 }
  220.             }
  221.            
  222.             /// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker.HandleType.CheckLeaks"]/*' />
  223.             /// <devdoc>
  224.             /// Checks and reports leaks for handle monitoring.
  225.             /// </devdoc>
  226.             public void CheckLeaks()
  227.             {
  228.                 lock (this) {
  229.                     bool reportedFirstLeak = false;
  230.                     if (handleCount > 0) {
  231.                         for (int i = 0; i < BUCKETS; i++) {
  232.                             HandleEntry e = buckets[i];
  233.                             while (e != null) {
  234.                                 if (!e.ignorableAsLeak) {
  235.                                     if (!reportedFirstLeak) {
  236.                                         Debug.WriteLine("\r\nHandle leaks detected for handles of type " + name + ":");
  237.                                         reportedFirstLeak = true;
  238.                                     }
  239.                                     Debug.WriteLine(e.ToString(this));
  240.                                 }
  241.                                 e = e.next;
  242.                             }
  243.                         }
  244.                     }
  245.                 }
  246.             }
  247.            
  248.             /// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker.HandleType.IgnoreCurrentHandlesAsLeaks"]/*' />
  249.             /// <devdoc>
  250.             /// Marks all the handles currently stored, as ignorable, so that they will not be reported as leaks later.
  251.             /// </devdoc>
  252.             public void IgnoreCurrentHandlesAsLeaks()
  253.             {
  254.                 lock (this) {
  255.                     if (handleCount > 0) {
  256.                         for (int i = 0; i < BUCKETS; i++) {
  257.                             HandleEntry e = buckets[i];
  258.                             while (e != null) {
  259.                                 e.ignorableAsLeak = true;
  260.                                 e = e.next;
  261.                             }
  262.                         }
  263.                     }
  264.                 }
  265.             }
  266.            
  267.             /// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker.HandleType.ComputeHash"]/*' />
  268.             /// <devdoc>
  269.             /// Computes the hash bucket for this handle.
  270.             /// </devdoc>
  271.             private int ComputeHash(IntPtr handle)
  272.             {
  273.                 return ((int)handle & 65535) % BUCKETS;
  274.             }
  275.            
  276.             /// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker.HandleType.Remove"]/*' />
  277.             /// <devdoc>
  278.             /// Removes the given handle from our monitor list.
  279.             /// </devdoc>
  280.             public bool Remove(IntPtr handle)
  281.             {
  282.                 lock (this) {
  283.                     int hash = ComputeHash(handle);
  284.                     if (CompModSwitches.HandleLeak.Level >= TraceLevel.Info) {
  285.                         Debug.WriteLine("-------------------------------------------------");
  286.                         Debug.WriteLine("Handle Releaseing: " + Convert.ToString((int)handle, 16));
  287.                         Debug.WriteLine("Handle Type : " + name);
  288.                         if (CompModSwitches.HandleLeak.Level >= TraceLevel.Verbose)
  289.                             Debug.WriteLine(Environment.StackTrace);
  290.                     }
  291.                     HandleEntry e = buckets[hash];
  292.                     HandleEntry last = null;
  293.                     while (e != null && e.handle != handle) {
  294.                         last = e;
  295.                         e = e.next;
  296.                     }
  297.                     if (e != null) {
  298.                         if (last == null) {
  299.                             buckets[hash] = e.next;
  300.                         }
  301.                         else {
  302.                             last.next = e.next;
  303.                         }
  304.                         handleCount--;
  305.                         return true;
  306.                     }
  307.                     return false;
  308.                 }
  309.             }
  310.            
  311.             /// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker.HandleType.HandleEntry"]/*' />
  312.             /// <devdoc>
  313.             /// Denotes a single entry in our handle list.
  314.             /// </devdoc>
  315.             [SuppressMessage("Microsoft.Design", "CA1049:TypesThatOwnNativeResourcesShouldBeDisposable")]
  316.             private class HandleEntry
  317.             {
  318.                
  319.                 [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")]
  320.                 public readonly IntPtr handle;
  321.                 public HandleEntry next;
  322.                 public readonly string callStack;
  323.                 public bool ignorableAsLeak;
  324.                
  325.                 /// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker.HandleType.HandleEntry.HandleEntry"]/*' />
  326.                 /// <devdoc>
  327.                 /// Creates a new handle entry
  328.                 /// </devdoc>
  329.                 public HandleEntry(HandleEntry next, IntPtr handle)
  330.                 {
  331.                     this.handle = handle;
  332.                     this.next = next;
  333.                    
  334.                     if (CompModSwitches.HandleLeak.Level > TraceLevel.Off) {
  335.                         this.callStack = Environment.StackTrace;
  336.                     }
  337.                     else {
  338.                         this.callStack = null;
  339.                     }
  340.                 }
  341.                
  342.                 /// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker.HandleType.HandleEntry.ToString"]/*' />
  343.                 /// <devdoc>
  344.                 /// Converts this handle to a printable string. the string consists
  345.                 /// of the handle value along with the callstack for it's
  346.                 /// allocation.
  347.                 /// </devdoc>
  348.                 public string ToString(HandleType type)
  349.                 {
  350.                     StackParser sp = new StackParser(callStack);
  351.                    
  352.                     // Discard all of the stack up to and including the "Handle.create" call
  353.                     //
  354.                     sp.DiscardTo("HandleCollector.Add");
  355.                    
  356.                     // Skip the next call as it is always a debug wrapper
  357.                     //
  358.                     sp.DiscardNext();
  359.                    
  360.                     // Now recreate the leak list with a lot of stack entries
  361.                     //
  362.                     sp.Truncate(40);
  363.                    
  364.                     string description = "";
  365.                     /*if (type.name.Equals("GDI") || type.name.Equals("HDC")) {
  366.                         int objectType = UnsafeNativeMethods.GetObjectType(new HandleRef(null, handle));
  367.                         switch (objectType) {
  368.                             case NativeMethods.OBJ_DC: description = "normal DC"; break;
  369.                             case NativeMethods.OBJ_MEMDC: description = "memory DC"; break;
  370.                             case NativeMethods.OBJ_METADC: description = "metafile DC"; break;
  371.                             case NativeMethods.OBJ_ENHMETADC: description = "enhanced metafile DC"; break;
  372.                             case NativeMethods.OBJ_PEN: description = "Pen"; break;
  373.                             case NativeMethods.OBJ_BRUSH: description = "Brush"; break;
  374.                             case NativeMethods.OBJ_PAL: description = "Palette"; break;
  375.                             case NativeMethods.OBJ_FONT: description = "Font"; break;
  376.                             case NativeMethods.OBJ_BITMAP: description = "Bitmap"; break;
  377.                             case NativeMethods.OBJ_REGION: description = "Region"; break;
  378.                             case NativeMethods.OBJ_METAFILE: description = "Metafile"; break;
  379.                             case NativeMethods.OBJ_EXTPEN: description = "Extpen"; break;
  380.                             default: description = "?"; break;
  381.                         }
  382.                         description = " (" + description + ")";
  383.                     }*/                   
  384.                    
  385. return Convert.ToString((int)handle, 16) + description + ": " + sp.ToString();
  386.                 }
  387.                
  388.                 /// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker.HandleType.HandleEntry.StackParser"]/*' />
  389.                 /// <devdoc>
  390.                 /// Simple stack parsing class to manipulate our callstack.
  391.                 /// </devdoc>
  392.                 private class StackParser
  393.                 {
  394.                     internal string releventStack;
  395.                     internal int startIndex;
  396.                     internal int endIndex;
  397.                     internal int length;
  398.                    
  399.                     /// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker.HandleType.HandleEntry.StackParser.StackParser"]/*' />
  400.                     /// <devdoc>
  401.                     /// Creates a new stackparser with the given callstack
  402.                     /// </devdoc>
  403.                     public StackParser(string callStack)
  404.                     {
  405.                         releventStack = callStack;
  406.                         length = releventStack.Length;
  407.                     }
  408.                    
  409.                     /// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker.HandleType.HandleEntry.StackParser.ContainsString"]/*' />
  410.                     /// <devdoc>
  411.                     /// Determines if the given string contains token. This is a case
  412.                     /// sensitive match.
  413.                     /// </devdoc>
  414.                     private static bool ContainsString(string str, string token)
  415.                     {
  416.                         int stringLength = str.Length;
  417.                         int tokenLength = token.Length;
  418.                        
  419.                         for (int s = 0; s < stringLength; s++) {
  420.                             int t = 0;
  421.                             while (t < tokenLength && str[s + t] == token[t]) {
  422.                                 t++;
  423.                             }
  424.                             if (t == tokenLength) {
  425.                                 return true;
  426.                             }
  427.                         }
  428.                         return false;
  429.                     }
  430.                    
  431.                     /// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker.HandleType.HandleEntry.StackParser.DiscardNext"]/*' />
  432.                     /// <devdoc>
  433.                     /// Discards the next line of the stack trace.
  434.                     /// </devdoc>
  435.                     public void DiscardNext()
  436.                     {
  437.                         GetLine();
  438.                     }
  439.                    
  440.                     /// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker.HandleType.HandleEntry.StackParser.DiscardTo"]/*' />
  441.                     /// <devdoc>
  442.                     /// Discards all lines up to and including the line that contains
  443.                     /// discardText.
  444.                     /// </devdoc>
  445.                     public void DiscardTo(string discardText)
  446.                     {
  447.                         while (startIndex < length) {
  448.                             string line = GetLine();
  449.                             if (line == null || ContainsString(line, discardText)) {
  450.                                 break;
  451.                             }
  452.                         }
  453.                     }
  454.                    
  455.                     /// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker.HandleType.HandleEntry.StackParser.GetLine"]/*' />
  456.                     /// <devdoc>
  457.                     /// Retrieves the next line of the stack.
  458.                     /// </devdoc>
  459.                     private string GetLine()
  460.                     {
  461.                         endIndex = releventStack.IndexOf('\r', startIndex);
  462.                         if (endIndex < 0) {
  463.                             endIndex = length - 1;
  464.                         }
  465.                        
  466.                         string line = releventStack.Substring(startIndex, endIndex - startIndex);
  467.                         char ch;
  468.                        
  469.                         while (endIndex < length && ((ch = releventStack[endIndex]) == '\r' || ch == '\n')) {
  470.                             endIndex++;
  471.                         }
  472.                         if (startIndex == endIndex)
  473.                             return null;
  474.                         startIndex = endIndex;
  475.                         line = line.Replace('\t', ' ');
  476.                         return line;
  477.                     }
  478.                    
  479.                     /// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker.HandleType.HandleEntry.StackParser.ToString"]/*' />
  480.                     /// <devdoc>
  481.                     /// Rereives the string of the parsed stack trace
  482.                     /// </devdoc>
  483.                     public override string ToString()
  484.                     {
  485.                         return releventStack.Substring(startIndex);
  486.                     }
  487.                    
  488.                     /// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker.HandleType.HandleEntry.StackParser.Truncate"]/*' />
  489.                     /// <devdoc>
  490.                     /// Truncates the stack trace, saving the given # of lines.
  491.                     /// </devdoc>
  492.                     public void Truncate(int lines)
  493.                     {
  494.                         string truncatedStack = "";
  495.                        
  496.                         while (lines-- > 0 && startIndex < length) {
  497.                             if (truncatedStack == null) {
  498.                                 truncatedStack = GetLine();
  499.                             }
  500.                             else {
  501.                                 truncatedStack += ": " + GetLine();
  502.                             }
  503.                             truncatedStack += Environment.NewLine;
  504.                         }
  505.                        
  506.                         releventStack = truncatedStack;
  507.                         startIndex = 0;
  508.                         endIndex = 0;
  509.                         length = releventStack.Length;
  510.                     }
  511.                 }
  512.             }
  513.         }
  514.        
  515.         //#endif // DEBUG
  516.     }
  517. }

Developer Fusion