|
//------------------------------------------------------------------------------
// <copyright file="DebugHandleTracker.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
/*
*/
namespace System.Internal {
using Microsoft.Win32;
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using Hashtable = System.Collections.Hashtable;
/// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker"]/*' />
/// <devdoc>
/// The job of this class is to collect and track handle usage in
/// windows forms. Ideally, a developer should never have to call dispose() on
/// any windows forms object. The problem in making this happen is in objects that
/// are very small to the VM garbage collector, but take up huge amounts
/// of resources to the system. A good example of this is a Win32 region
/// handle. To the VM, a Region object is a small six ubyte object, so there
/// isn't much need to garbage collect it anytime soon. To Win32, however,
/// a region handle consumes expensive USER and GDI resources. Ideally we
/// would like to be able to mark an object as "expensive" so it uses a different
/// garbage collection algorithm. In absence of that, we use the HandleCollector class, which
/// runs a daemon thread to garbage collect when handle usage goes up.
/// </devdoc>
/// <internalonly/>
internal class DebugHandleTracker {
//#if DEBUG
private static Hashtable handleTypes = new Hashtable();
private static DebugHandleTracker tracker;
static DebugHandleTracker() {
tracker = new DebugHandleTracker();
if (CompModSwitches.HandleLeak.Level > TraceLevel.Off || CompModSwitches.TraceCollect.Enabled) {
System.Internal.HandleCollector.HandleAdded += new System.Internal.HandleChangeEventHandler(tracker.OnHandleAdd);
System.Internal.HandleCollector.HandleRemoved += new System.Internal.HandleChangeEventHandler(tracker.OnHandleRemove);
}
}
private DebugHandleTracker() {
}
private static object internalSyncObject = new object();
/// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker.IgnoreCurrentHandlesAsLeaks"]/*' />
/// <devdoc>
/// All handles available at this time will be not be considered as leaks
/// when CheckLeaks is called to report leaks.
/// </devdoc>
/** @conditional(DEBUG) */
public static void IgnoreCurrentHandlesAsLeaks() {
lock(internalSyncObject) {
if (CompModSwitches.HandleLeak.Level >= TraceLevel.Warning) {
HandleType[] types = new HandleType[handleTypes.Values.Count];
handleTypes.Values.CopyTo(types, 0);
for (int i = 0; i < types.Length; i++) {
if (types[i] != null) {
types[i].IgnoreCurrentHandlesAsLeaks();
}
}
}
}
}
/// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker.CheckLeaks"]/*' />
/// <devdoc>
/// Called at shutdown to check for handles that are currently allocated.
/// Normally, there should be none. This will print a list of all
/// handle leaks.
/// </devdoc>
/** @conditional(DEBUG) */
[SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")]
public static void CheckLeaks() {
lock(internalSyncObject) {
if (CompModSwitches.HandleLeak.Level >= TraceLevel.Warning) {
GC.Collect();
GC.WaitForPendingFinalizers();
HandleType[] types = new HandleType[handleTypes.Values.Count];
handleTypes.Values.CopyTo(types, 0);
Debug.WriteLine("------------Begin--CheckLeaks--------------------");
for (int i = 0; i < types.Length; i++) {
if (types[i] != null) {
types[i].CheckLeaks();
}
}
Debug.WriteLine("-------------End--CheckLeaks---------------------");
}
}
}
/// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker.Initialize"]/*' />
/// <devdoc>
/// Ensures leak detection has been initialized.
/// </devdoc>
/** @conditional(DEBUG) */
public static void Initialize() {
// Calling this method forces the class to be loaded, thus running the
// static constructor which does all the work.
}
/// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker.OnHandleAdd"]/*' />
/// <devdoc>
/// Called by the Win32 handle collector when a new handle is created.
/// </devdoc>
/** @conditional(DEBUG) */
private void OnHandleAdd(string handleName, IntPtr handle, int handleCount) {
HandleType type = (HandleType)handleTypes[handleName];
if (type == null) {
type = new HandleType(handleName);
handleTypes[handleName] = type;
}
type.Add(handle);
}
/// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker.OnHandleRemove"]/*' />
/// <devdoc>
/// Called by the Win32 handle collector when a new handle is created.
/// </devdoc>
/** @conditional(DEBUG) */
private void OnHandleRemove(string handleName, IntPtr handle, int HandleCount) {
HandleType type = (HandleType)handleTypes[handleName];
bool removed = false;
if (type != null) {
removed = type.Remove(handle);
}
if (!removed) {
if (CompModSwitches.HandleLeak.Level >= TraceLevel.Error) {
// It seems to me we shouldn't call HandleCollector.Remove more than once
// for a given handle, but we do just that for HWND's (NativeWindow.DestroyWindow
// and Control.WmNCDestroy).
Debug.WriteLine("*************************************************");
Debug.WriteLine("While removing, couldn't find handle: " + Convert.ToString(unchecked((int)handle), 16));
Debug.WriteLine("Handle Type : " + handleName);
Debug.WriteLine(Environment.StackTrace);
Debug.WriteLine("-------------------------------------------------");
}
}
}
/// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker.HandleType"]/*' />
/// <devdoc>
/// Represents a specific type of handle.
/// </devdoc>
private class HandleType {
public readonly string name;
private int handleCount;
private HandleEntry[] buckets;
private const int BUCKETS = 10;
/// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker.HandleType.HandleType"]/*' />
/// <devdoc>
/// Creates a new handle type.
/// </devdoc>
public HandleType(string name) {
this.name = name;
this.buckets = new HandleEntry[BUCKETS];
}
/// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker.HandleType.Add"]/*' />
/// <devdoc>
/// Adds a handle to this handle type for monitoring.
/// </devdoc>
public void Add(IntPtr handle) {
lock(this) {
int hash = ComputeHash(handle);
if (CompModSwitches.HandleLeak.Level >= TraceLevel.Info) {
Debug.WriteLine("-------------------------------------------------");
Debug.WriteLine("Handle Allocating: " + Convert.ToString(unchecked((int)handle), 16));
Debug.WriteLine("Handle Type : " + name);
if (CompModSwitches.HandleLeak.Level >= TraceLevel.Verbose)
Debug.WriteLine(Environment.StackTrace);
}
HandleEntry entry = buckets[hash];
while (entry != null) {
Debug.Assert(entry.handle != handle, "Duplicate handle of type " + name);
entry = entry.next;
}
buckets[hash] = new HandleEntry(buckets[hash], handle);
handleCount++;
}
}
/// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker.HandleType.CheckLeaks"]/*' />
/// <devdoc>
/// Checks and reports leaks for handle monitoring.
/// </devdoc>
public void CheckLeaks() {
lock(this) {
bool reportedFirstLeak = false;
if (handleCount > 0) {
for (int i = 0; i < BUCKETS; i++) {
HandleEntry e = buckets[i];
while (e != null) {
if (!e.ignorableAsLeak) {
if (!reportedFirstLeak) {
Debug.WriteLine("\r\nHandle leaks detected for handles of type " + name + ":");
reportedFirstLeak = true;
}
Debug.WriteLine(e.ToString(this));
}
e = e.next;
}
}
}
}
}
/// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker.HandleType.IgnoreCurrentHandlesAsLeaks"]/*' />
/// <devdoc>
/// Marks all the handles currently stored, as ignorable, so that they will not be reported as leaks later.
/// </devdoc>
public void IgnoreCurrentHandlesAsLeaks() {
lock(this) {
if (handleCount > 0) {
for (int i = 0; i < BUCKETS; i++) {
HandleEntry e = buckets[i];
while (e != null) {
e.ignorableAsLeak = true;
e = e.next;
}
}
}
}
}
/// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker.HandleType.ComputeHash"]/*' />
/// <devdoc>
/// Computes the hash bucket for this handle.
/// </devdoc>
private int ComputeHash(IntPtr handle) {
return(unchecked((int)handle) & 0xFFFF) % BUCKETS;
}
/// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker.HandleType.Remove"]/*' />
/// <devdoc>
/// Removes the given handle from our monitor list.
/// </devdoc>
public bool Remove(IntPtr handle) {
lock(this) {
int hash = ComputeHash(handle);
if (CompModSwitches.HandleLeak.Level >= TraceLevel.Info) {
Debug.WriteLine("-------------------------------------------------");
Debug.WriteLine("Handle Releaseing: " + Convert.ToString(unchecked((int)handle), 16));
Debug.WriteLine("Handle Type : " + name);
if (CompModSwitches.HandleLeak.Level >= TraceLevel.Verbose)
Debug.WriteLine(Environment.StackTrace);
}
HandleEntry e = buckets[hash];
HandleEntry last = null;
while (e != null && e.handle != handle) {
last = e;
e = e.next;
}
if (e != null) {
if (last == null) {
buckets[hash] = e.next;
}
else {
last.next = e.next;
}
handleCount--;
return true;
}
return false;
}
}
/// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker.HandleType.HandleEntry"]/*' />
/// <devdoc>
/// Denotes a single entry in our handle list.
/// </devdoc>
[SuppressMessage("Microsoft.Design", "CA1049:TypesThatOwnNativeResourcesShouldBeDisposable")]
private class HandleEntry {
[SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")]
public readonly IntPtr handle;
public HandleEntry next;
public readonly string callStack;
public bool ignorableAsLeak;
/// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker.HandleType.HandleEntry.HandleEntry"]/*' />
/// <devdoc>
/// Creates a new handle entry
/// </devdoc>
public HandleEntry(HandleEntry next, IntPtr handle) {
this.handle = handle;
this.next = next;
if (CompModSwitches.HandleLeak.Level > TraceLevel.Off) {
this.callStack = Environment.StackTrace;
}
else {
this.callStack = null;
}
}
/// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker.HandleType.HandleEntry.ToString"]/*' />
/// <devdoc>
/// Converts this handle to a printable string. the string consists
/// of the handle value along with the callstack for it's
/// allocation.
/// </devdoc>
public string ToString(HandleType type) {
StackParser sp = new StackParser(callStack);
// Discard all of the stack up to and including the "Handle.create" call
//
sp.DiscardTo("HandleCollector.Add");
// Skip the next call as it is always a debug wrapper
//
sp.DiscardNext();
// Now recreate the leak list with a lot of stack entries
//
sp.Truncate(40);
string description = "";
/*if (type.name.Equals("GDI") || type.name.Equals("HDC")) {
int objectType = UnsafeNativeMethods.GetObjectType(new HandleRef(null, handle));
switch (objectType) {
case NativeMethods.OBJ_DC: description = "normal DC"; break;
case NativeMethods.OBJ_MEMDC: description = "memory DC"; break;
case NativeMethods.OBJ_METADC: description = "metafile DC"; break;
case NativeMethods.OBJ_ENHMETADC: description = "enhanced metafile DC"; break;
case NativeMethods.OBJ_PEN: description = "Pen"; break;
case NativeMethods.OBJ_BRUSH: description = "Brush"; break;
case NativeMethods.OBJ_PAL: description = "Palette"; break;
case NativeMethods.OBJ_FONT: description = "Font"; break;
case NativeMethods.OBJ_BITMAP: description = "Bitmap"; break;
case NativeMethods.OBJ_REGION: description = "Region"; break;
case NativeMethods.OBJ_METAFILE: description = "Metafile"; break;
case NativeMethods.OBJ_EXTPEN: description = "Extpen"; break;
default: description = "?"; break;
}
description = " (" + description + ")";
}*/
return Convert.ToString(unchecked((int)handle), 16) + description + ": " + sp.ToString();
}
/// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker.HandleType.HandleEntry.StackParser"]/*' />
/// <devdoc>
/// Simple stack parsing class to manipulate our callstack.
/// </devdoc>
private class StackParser {
internal string releventStack;
internal int startIndex;
internal int endIndex;
internal int length;
/// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker.HandleType.HandleEntry.StackParser.StackParser"]/*' />
/// <devdoc>
/// Creates a new stackparser with the given callstack
/// </devdoc>
public StackParser(string callStack) {
releventStack = callStack;
length = releventStack.Length;
}
/// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker.HandleType.HandleEntry.StackParser.ContainsString"]/*' />
/// <devdoc>
/// Determines if the given string contains token. This is a case
/// sensitive match.
/// </devdoc>
private static bool ContainsString(string str, string token) {
int stringLength = str.Length;
int tokenLength = token.Length;
for (int s = 0; s < stringLength; s++) {
int t = 0;
while (t < tokenLength && str[s + t] == token[t]) {
t++;
}
if (t == tokenLength) {
return true;
}
}
return false;
}
/// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker.HandleType.HandleEntry.StackParser.DiscardNext"]/*' />
/// <devdoc>
/// Discards the next line of the stack trace.
/// </devdoc>
public void DiscardNext() {
GetLine();
}
/// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker.HandleType.HandleEntry.StackParser.DiscardTo"]/*' />
/// <devdoc>
/// Discards all lines up to and including the line that contains
/// discardText.
/// </devdoc>
public void DiscardTo(string discardText) {
while (startIndex < length) {
string line = GetLine();
if (line == null || ContainsString(line, discardText)) {
break;
}
}
}
/// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker.HandleType.HandleEntry.StackParser.GetLine"]/*' />
/// <devdoc>
/// Retrieves the next line of the stack.
/// </devdoc>
private string GetLine() {
endIndex = releventStack.IndexOf('\r', startIndex);
if (endIndex < 0) {
endIndex = length - 1;
}
string line = releventStack.Substring(startIndex, endIndex - startIndex);
char ch;
while (endIndex < length && ((ch = releventStack[endIndex]) == '\r' || ch == '\n')) {
endIndex++;
}
if (startIndex == endIndex) return null;
startIndex = endIndex;
line = line.Replace('\t', ' ');
return line;
}
/// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker.HandleType.HandleEntry.StackParser.ToString"]/*' />
/// <devdoc>
/// Rereives the string of the parsed stack trace
/// </devdoc>
public override string ToString() {
return releventStack.Substring(startIndex);
}
/// <include file='doc\DebugHandleTracker.uex' path='docs/doc[@for="DebugHandleTracker.HandleType.HandleEntry.StackParser.Truncate"]/*' />
/// <devdoc>
/// Truncates the stack trace, saving the given # of lines.
/// </devdoc>
public void Truncate(int lines) {
string truncatedStack = "";
while (lines-- > 0 && startIndex < length) {
if (truncatedStack == null) {
truncatedStack = GetLine();
}
else {
truncatedStack += ": " + GetLine();
}
truncatedStack += Environment.NewLine;
}
releventStack = truncatedStack;
startIndex = 0;
endIndex = 0;
length = releventStack.Length;
}
}
}
}
//#endif // DEBUG
}
}
|