/// <summary> /// Enumerates GCRoots of a given object. Similar to !gcroot. /// </summary> /// <param name="target">The target object to search for GC rooting.</param> /// <param name="unique">Whether to only return fully unique paths.</param> /// <param name="cancelToken">A cancellation token to stop enumeration.</param> /// <returns>An enumeration of all GC roots found for target.</returns> public IEnumerable <RootPath> EnumerateGCRoots(ulong target, bool unique, CancellationToken cancelToken) { _heap.BuildDependentHandleMap(cancelToken); long totalObjects = _heap.TotalObjects; long lastObjectReported = 0; bool parallel = AllowParallelSearch && IsFullyCached && _maxTasks > 0; Task <Tuple <LinkedList <ClrObject>, ClrRoot> >[] tasks; ObjectSet processedObjects; Dictionary <ulong, LinkedListNode <ClrObject> > knownEndPoints = new Dictionary <ulong, LinkedListNode <ClrObject> >(); if (parallel) { processedObjects = new ParallelObjectSet(_heap); } else { processedObjects = new ObjectSet(_heap); } int initial = 0; tasks = new Task <Tuple <LinkedList <ClrObject>, ClrRoot> > [_maxTasks]; foreach (ClrHandle handle in _heap.EnumerateStrongHandles()) { Debug.Assert(handle.HandleType != HandleType.Dependent); Debug.Assert(handle.Object != 0); if (processedObjects.Contains(handle.Object)) { continue; } Debug.Assert(_heap.GetObjectType(handle.Object) == handle.Type); if (parallel) { var task = PathToParallel(processedObjects, knownEndPoints, handle, target, unique, cancelToken); if (initial < tasks.Length) { tasks[initial++] = task; } else { int i = Task.WaitAny(tasks); var completed = tasks[i]; tasks[i] = task; if (completed.Result.Item1 != null) { yield return new RootPath() { Root = completed.Result.Item2, Path = completed.Result.Item1.ToArray() } } ; } } else { var path = PathTo(processedObjects, knownEndPoints, new ClrObject(handle.Object, handle.Type), target, unique, false, cancelToken).FirstOrDefault(); if (path != null) { yield return new RootPath() { Root = GetHandleRoot(handle), Path = path.ToArray() } } ; } ReportObjectCount(processedObjects.Count, ref lastObjectReported, totalObjects); } foreach (ClrRoot root in _heap.EnumerateStackRoots()) { if (!processedObjects.Contains(root.Object)) { Debug.Assert(_heap.GetObjectType(root.Object) == root.Type); if (parallel) { var task = PathToParallel(processedObjects, knownEndPoints, root, target, unique, cancelToken); if (initial < tasks.Length) { tasks[initial++] = task; } else { int i = Task.WaitAny(tasks); var completed = tasks[i]; tasks[i] = task; if (completed.Result.Item1 != null) { yield return new RootPath() { Root = completed.Result.Item2, Path = completed.Result.Item1.ToArray() } } ; } } else { var path = PathTo(processedObjects, knownEndPoints, new ClrObject(root.Object, root.Type), target, unique, false, cancelToken).FirstOrDefault(); if (path != null) { yield return new RootPath() { Root = root, Path = path.ToArray() } } ; } ReportObjectCount(processedObjects.Count, ref lastObjectReported, totalObjects); } } foreach (Tuple <LinkedList <ClrObject>, ClrRoot> result in WhenEach(tasks)) { ReportObjectCount(processedObjects.Count, ref lastObjectReported, totalObjects); yield return(new RootPath() { Root = result.Item2, Path = result.Item1.ToArray() }); } ReportObjectCount(totalObjects, ref lastObjectReported, totalObjects); }
/// <summary> /// Enumerates GCRoots of a given object. Similar to !gcroot. /// </summary> /// <param name="target">The target object to search for GC rooting.</param> /// <param name="unique">Whether to only return fully unique paths.</param> /// <param name="maxDegreeOfParallelism">The number of threads this class is allowed to use to calculate the result. /// Setting this to 1 will cause the algorithm to run on the current thread.</param> /// <param name="roots">The roots to consider. You can pass ClrMD.</param> /// <param name="cancellationToken">A cancellation token to stop enumeration.</param> /// <returns>An enumeration of all GC roots found for target.</returns> public IEnumerable <GCRootPath> EnumerateGCRoots(ulong target, bool unique, int maxDegreeOfParallelism, IEnumerable <IClrRoot> roots, CancellationToken cancellationToken = default) { if (roots is null) { throw new ArgumentNullException(nameof(roots)); } bool parallel = Heap.Runtime.IsThreadSafe && maxDegreeOfParallelism > 1; Dictionary <ulong, LinkedListNode <ClrObject> > knownEndPoints = new Dictionary <ulong, LinkedListNode <ClrObject> >() { { target, new LinkedListNode <ClrObject>(Heap.GetObject(target)) } }; if (!parallel) { int count = 0; ObjectSet processedObjects = new ObjectSet(Heap); foreach (IClrRoot root in roots) { LinkedList <ClrObject> path = PathsTo(processedObjects, knownEndPoints, root.Object, target, unique, cancellationToken).FirstOrDefault(); if (path != null) { yield return(new GCRootPath(root, path.ToImmutableArray())); } if (count != processedObjects.Count) { count = processedObjects.Count; ProgressUpdated?.Invoke(this, count); } } } else { ParallelObjectSet processedObjects = new ParallelObjectSet(Heap); ConcurrentQueue <GCRootPath> results = new ConcurrentQueue <GCRootPath>(); using BlockingCollection <IClrRoot?> queue = new BlockingCollection <IClrRoot?>(); Thread[] threads = new Thread[Math.Min(maxDegreeOfParallelism, Environment.ProcessorCount)]; for (int i = 0; i < threads.Length; i++) { threads[i] = new Thread(() => WorkerThread(queue, results, processedObjects, knownEndPoints, target, all: true, unique, cancellationToken)) { Name = "GCRoot Worker Thread" }; threads[i].Start(); } foreach (IClrRoot root in roots) { queue.Add(root); } // Add one sentinal value for every thread for (int i = 0; i < threads.Length; i++) { queue.Add(null); } int count = 0; // Worker threads end when they have run out of roots to process. While we are waiting for them to exit, yield return // any results they've found. We'll use a 100 msec timeout because processing roots is slooooow and finding a root is // rare. There's no reason to check these results super quickly and starve worker threads. for (int i = 0; i < threads.Length; i++) { while (!threads[i].Join(100)) { while (results.TryDequeue(out GCRootPath result)) { yield return(result); } } if (count != processedObjects.Count) { count = processedObjects.Count; ProgressUpdated?.Invoke(this, count); } } // We could have raced to put an object in the results queue while joining the last thread, so we need to drain the // results queue one last time. while (results.TryDequeue(out GCRootPath result)) { yield return(result); } if (count != processedObjects.Count) { count = processedObjects.Count; ProgressUpdated?.Invoke(this, count); } } }