private IEnumerable <LinkedList <ClrObject> > PathTo(ObjectSet seen, Dictionary <ulong, LinkedListNode <ClrObject> > knownEndPoints, ClrObject source, ulong target, bool unique, bool parallel, CancellationToken cancelToken) { seen.Add(source.Address); if (source.Type == null) { yield break; } LinkedList <PathEntry> path = new LinkedList <PathEntry>(); if (source.Address == target) { path.AddLast(new PathEntry() { Object = source }); yield return(GetResult(knownEndPoints, path, null, target)); yield break; } path.AddLast(new PathEntry() { Object = source, Todo = GetRefs(seen, knownEndPoints, source, target, unique, parallel, cancelToken, out bool foundTarget, out LinkedListNode <ClrObject> foundEnding) });
private IEnumerable <LinkedList <ClrObject> > PathTo(ObjectSet seen, Dictionary <ulong, LinkedListNode <ClrObject> > knownEndPoints, ClrObject source, ulong target, bool unique, bool parallel, CancellationToken cancelToken) { seen.Add(source.Address); bool foundTarget; LinkedListNode <ClrObject> foundEnding; if (source.Type == null) { yield break; } LinkedList <PathEntry> path = new LinkedList <PathEntry>(); if (source.Address == target) { path.AddLast(new PathEntry() { Object = source }); yield return(GetResult(knownEndPoints, path, null, target)); yield break; } path.AddLast(new PathEntry() { Object = source, Todo = GetRefs(seen, knownEndPoints, source, target, unique, parallel, cancelToken, out foundTarget, out foundEnding) }); // Did the 'start' object point directly to 'end'? If so, early out. if (foundTarget) { path.AddLast(new PathEntry() { Object = ClrObject.Create(target, _heap.GetObjectType(target)) }); yield return(GetResult(knownEndPoints, path, null, target)); } else if (foundEnding != null) { yield return(GetResult(knownEndPoints, path, foundEnding, target)); } while (path.Count > 0) { cancelToken.ThrowIfCancellationRequested(); TraceFullPath(null, path); var last = path.Last.Value; if (last.Todo.Count == 0) { // We've exhausted all children and didn't find the target. Remove this node // and continue. path.RemoveLast(); } else { // We loop here in case we encounter an object we've already processed (or if // we can't get an object's type...inconsistent heap happens sometimes). do { cancelToken.ThrowIfCancellationRequested(); ClrObject next = last.Todo.Pop(); // Now that we are in the process of adding 'next' to the path, don't ever consider // this object in the future. if (!seen.Add(next.Address)) { continue; } // We should never reach the 'end' here, as we always check if we found the target // value when adding refs below. Debug.Assert(next.Address != target); PathEntry nextPathEntry = new PathEntry() { Object = next, Todo = GetRefs(seen, knownEndPoints, next, target, unique, parallel, cancelToken, out foundTarget, out foundEnding) }; path.AddLast(nextPathEntry); // If we found the target object while enumerating refs of the current object, we are done. if (foundTarget) { path.AddLast(new PathEntry() { Object = ClrObject.Create(target, _heap.GetObjectType(target)) }); TraceFullPath("FoundTarget", path); yield return(GetResult(knownEndPoints, path, null, target)); path.RemoveLast(); path.RemoveLast(); } else if (foundEnding != null) { TraceFullPath(path, foundEnding); yield return(GetResult(knownEndPoints, path, foundEnding, target)); path.RemoveLast(); } // Now that we've added a new entry to 'path', break out of the do/while that's looping through Todo. break; }while (last.Todo.Count > 0); } } }
private IEnumerable <LinkedList <ClrObject> > PathsTo( ObjectSet deadEnds, Dictionary <ulong, LinkedListNode <ClrObject> >?knownEndPoints, ClrObject source, ulong target, bool unique, CancellationToken cancellationToken) { /* knownEndPoints: A set of objects that are known to point to the target, once we see one of * these, we can end the search early and return the current path plus the * nodes there. * * deadEnds When unique == false, these are Objects we've fully processed and know doesn't * point to the target object. When unique == true, deadEnds are updated to include * objects that other threads are processing, ensuring we don't yield duplicate * roots. Note that we must be careful to ONLY add objects to this list when we are * sure we've exhausted all potential paths because parallel threads use this same * set. * * path: The current path we are considering. As we walk through objects in the rooting chain, * we will add a new node to this list with the object in question and create a stack of * all objects it points to. This is a depth-first search, so we will search as deeply * as we can, and pop off objects from path as soon as the stack of references is empty. * If we find that the reference stack is empty and we didn't find the target/known end * point, then we'll mark the object in deadEnds. * * processing: The list of objects we are currently considering on this thread. Since objects * can contain circular references, we need to know we shouldn't re-consider objects * we encounter while walking objects. * * It's important to note that multiple threads may race and consider the same path at the same * time. This is expected (and similar to how the GC operates). The thing we have to make sure * of is that we only add objects to knownEndPoints/deadEnds when we are 100% sure of the result * because it will directly affect other threads walking these paths. */ HashSet <ulong> processing = new HashSet <ulong>() { source.Address }; LinkedList <PathEntry> path = new LinkedList <PathEntry>(); if (knownEndPoints != null) { lock (knownEndPoints) { if (knownEndPoints.TryGetValue(source.Address, out LinkedListNode <ClrObject>?ending)) { if (!unique || ending.Value.Address == target) { yield return(GetResult(ending)); } yield break; } } } if (unique && !deadEnds.Add(source.Address)) { yield break; } if (source.Type is null) { yield break; } if (source.Address == target) { path.AddLast(new PathEntry { Object = source }); yield return(GetResult()); yield break; } path.AddLast( new PathEntry { Object = source, Todo = GetRefs(source, out bool foundTarget, out LinkedListNode <ClrObject>?foundEnding) });