SharpGen.Runtime/Diagnostics/ObjectTracker.cs (178 lines of code) (raw):

// Copyright (c) 2010-2014 SharpDX - Alexandre Mutel // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. using System; using System.Collections.Generic; using System.Text; using System.Threading; namespace SharpGen.Runtime.Diagnostics { /// <summary> /// Event args for <see cref="CppObject"/> used by <see cref="ObjectTracker"/>. /// </summary> public class CppObjectEventArgs : EventArgs { /// <summary> /// The object being tracked/untracked. /// </summary> public CppObject Object { get; } /// <summary> /// Initializes a new instance of the <see cref="CppObjectEventArgs"/> class. /// </summary> /// <param name="o">The o.</param> public CppObjectEventArgs(CppObject o) { Object = o; } } /// <summary> /// Track all allocated objects. /// </summary> public static partial class ObjectTracker { private static Dictionary<IntPtr, List<ObjectReference>> _processGlobalObjectReferences; private static readonly ThreadLocal<Dictionary<IntPtr, List<ObjectReference>>> ThreadStaticObjectReferences = new(static () => new Dictionary<IntPtr, List<ObjectReference>>(), false); /// <summary> /// Occurs when a CppObject is tracked. /// </summary> public static event EventHandler<CppObjectEventArgs> Tracked; /// <summary> /// Occurs when a CppObject is untracked. /// </summary> public static event EventHandler<CppObjectEventArgs> UnTracked; private static Dictionary<IntPtr, List<ObjectReference>> ObjectReferences => Configuration.UseThreadStaticObjectTracking ? ThreadStaticObjectReferences.Value : _processGlobalObjectReferences ??= new Dictionary<IntPtr, List<ObjectReference>>(); /// <summary> /// Tracks the specified C++ object. /// </summary> /// <param name="cppObject">The C++ object.</param> public static void Track(CppObject cppObject) { if (cppObject == null) return; var nativePointer = cppObject.NativePointer; if (nativePointer == IntPtr.Zero) return; var objectReferences = ObjectReferences; lock (objectReferences) TrackImpl(cppObject, nativePointer, objectReferences); // Fire Tracked event. OnTracked(cppObject); } private static void TrackImpl(CppObject cppObject, IntPtr nativePointer, Dictionary<IntPtr, List<ObjectReference>> objectReferences) { if (!objectReferences.TryGetValue(nativePointer, out var referenceList)) { referenceList = new List<ObjectReference>(); objectReferences.Add(nativePointer, referenceList); } referenceList.Add( new ObjectReference( DateTime.Now, cppObject, StackTraceProvider != null ? StackTraceProvider() : string.Empty ) ); } /// <summary> /// Finds a list of object reference from a specified C++ object pointer. /// </summary> /// <param name="objPtr">The C++ object pointer.</param> /// <returns>A list of object reference</returns> public static List<ObjectReference> Find(IntPtr objPtr) { lock (ObjectReferences) { // Object is already tracked if (ObjectReferences.TryGetValue(objPtr, out var referenceList)) return new List<ObjectReference>(referenceList); } return new List<ObjectReference>(); } /// <summary> /// Finds the object reference for a specific COM object. /// </summary> /// <param name="cppObject">The COM object.</param> /// <returns>An object reference</returns> public static ObjectReference Find(CppObject cppObject) => Find(cppObject, cppObject.NativePointer); internal static ObjectReference Find(CppObject cppObject, IntPtr nativePointer) { lock (ObjectReferences) { if (ObjectReferences.TryGetValue(nativePointer, out var referenceList)) { foreach (var objectReference in referenceList) { if (ReferenceEquals(objectReference.Object.Target, cppObject)) return objectReference; } } } return null; } /// <summary> /// Untracks the specified COM object. /// </summary> /// <param name="cppObject">The COM object.</param> public static void UnTrack(CppObject cppObject) => Untrack(cppObject, cppObject.NativePointer); internal static void Untrack(CppObject cppObject, IntPtr nativePointer) { if (cppObject == null || nativePointer == IntPtr.Zero) return; var objectReferences = ObjectReferences; bool foundTracked; lock (objectReferences) { foundTracked = UntrackImpl(cppObject, nativePointer, objectReferences); } if (foundTracked) { // Fire UnTracked event OnUnTracked(cppObject); } } private static bool UntrackImpl(CppObject cppObject, IntPtr nativePointer, Dictionary<IntPtr, List<ObjectReference>> objectReferences) { var foundTracked = objectReferences.TryGetValue(nativePointer, out var referenceList); if (!foundTracked) return false; // Object is tracked, remove from reference list for (int i = referenceList.Count - 1; i >= 0; i--) { var objectReference = referenceList[i]; if (ReferenceEquals(objectReference.Object.Target, cppObject) || !objectReference.IsAlive) referenceList.RemoveAt(i); } // Remove empty list if (referenceList.Count == 0) objectReferences.Remove(nativePointer); return true; } internal static void MigrateNativePointer(CppObject cppObject, IntPtr oldNativePointer, IntPtr newNativePointer) { if (cppObject == null) return; var hasOldNativePointer = oldNativePointer != IntPtr.Zero; var hasNewNativePointer = newNativePointer != IntPtr.Zero; if (!hasOldNativePointer && !hasNewNativePointer) return; var objectReferences = ObjectReferences; lock (objectReferences) { if (hasOldNativePointer) UntrackImpl(cppObject, oldNativePointer, objectReferences); if (hasNewNativePointer) TrackImpl(cppObject, newNativePointer, objectReferences); } } /// <summary> /// Reports all COM object that are active and not yet disposed. /// </summary> public static List<ObjectReference> FindActiveObjects() { var activeObjects = new List<ObjectReference>(); lock (ObjectReferences) { foreach (var referenceList in ObjectReferences.Values) { foreach (var objectReference in referenceList) { if (objectReference.IsAlive) activeObjects.Add(objectReference); } } } return activeObjects; } /// <summary> /// Reports all C++ objects that are active and not yet disposed. /// </summary> public static string ReportActiveObjects() { var text = new StringBuilder(); var count = 0; var countPerType = new Dictionary<string, int>(); foreach (var findActiveObject in FindActiveObjects()) { var findActiveObjectStr = findActiveObject.ToString(); if (string.IsNullOrEmpty(findActiveObjectStr)) continue; text.AppendFormat("[{0}]: {1}", count++, findActiveObjectStr); var target = findActiveObject.Object.Target; if (target == null) continue; var targetType = target.GetType().Name; countPerType[targetType] = countPerType.TryGetValue(targetType, out var typeCount) ? typeCount + 1 : 1; } var keys = new List<string>(countPerType.Keys); keys.Sort(); text.AppendLine(); text.AppendLine("Count per Type:"); foreach (var key in keys) { text.AppendFormat("{0} : {1}", key, countPerType[key]); text.AppendLine(); } return text.ToString(); } private static void OnTracked(CppObject obj) { Tracked?.Invoke(null, new CppObjectEventArgs(obj)); } private static void OnUnTracked(CppObject obj) { UnTracked?.Invoke(null, new CppObjectEventArgs(obj)); } } }