Skip to content

Commit 9f6b366

Browse files
author
Lifeng Lu
committed
clean up the pending table when task is done
also add a new ETW event, when we have circular dependencies in the graph.
1 parent bda22e4 commit 9f6b366

File tree

3 files changed

+51
-8
lines changed

3 files changed

+51
-8
lines changed

src/Microsoft.VisualStudio.Threading/JoinableTask.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1143,6 +1143,7 @@ private bool TryDequeueSelfOrDependencies(bool onMainThread, ref HashSet<IJoinab
11431143
if (!foundWork && this.PotentialUnreachableDependents != null)
11441144
{
11451145
JoinableTaskDependencyGraph.RemoveUnreachableDependentItems(this, this.PotentialUnreachableDependents, visitedNodes);
1146+
this.PotentialUnreachableDependents = null;
11461147
}
11471148
}
11481149

src/Microsoft.VisualStudio.Threading/JoinableTaskDependencyGraph.cs

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ internal static void OnTaskCompleted(IJoinableTaskDependent taskItem)
165165
{
166166
Requires.NotNull(taskItem, nameof(taskItem));
167167
Assumes.True(Monitor.IsEntered(taskItem.JoinableTaskContext.SyncContextLock));
168-
taskItem.GetJoinableTaskDependentData().OnTaskCompleted();
168+
taskItem.GetJoinableTaskDependentData().OnTaskCompleted(taskItem);
169169
}
170170

171171
/// <summary>
@@ -224,6 +224,7 @@ internal static HashSet<JoinableTask> GetDependentTasksFromCandidates(IEnumerabl
224224
/// Computes dependency graph to clean up all potential unreachable dependents items.
225225
/// </summary>
226226
/// <param name="syncTask">A thread blocking sychornizing task.</param>
227+
/// <param name="allReachableNodes">Returns all reachable nodes in the connected dependency graph, if unreachable dependency is found.</param>
227228
/// <returns>True if it removes any unreachable items.</returns>
228229
internal static bool CleanUpPotentialUnreachableDependentItems(JoinableTask syncTask, [MaybeNullWhen(false)] out HashSet<IJoinableTaskDependent>? allReachableNodes)
229230
{
@@ -234,21 +235,21 @@ internal static bool CleanUpPotentialUnreachableDependentItems(JoinableTask sync
234235
// To get rid of those loops, if a task still tracks the synchronous task after reducing
235236
// the reference count, we will calculate the entire reachable tree from the root. That will
236237
// tell us the exactly tasks which need track the synchronous task, and we will clean up the rest.
237-
HashSet<IJoinableTaskDependent>? unreachableItems = syncTask.PotentialUnreachableDependents;
238-
if (unreachableItems is object)
238+
HashSet<IJoinableTaskDependent>? possibleUnreachableItems = syncTask.PotentialUnreachableDependents;
239+
if (possibleUnreachableItems is object)
239240
{
240241
var reachableNodes = new HashSet<IJoinableTaskDependent>();
241242
var syncTaskItem = (IJoinableTaskDependent)syncTask;
242243

243-
JoinableTaskDependentData.ComputeSelfAndDescendentOrJoinedJobsAndRemainTasks(syncTaskItem, reachableNodes, unreachableItems);
244+
JoinableTaskDependentData.ComputeSelfAndDescendentOrJoinedJobsAndRemainTasks(syncTaskItem, reachableNodes, possibleUnreachableItems);
244245

245246
syncTask.PotentialUnreachableDependents = null;
246247
allReachableNodes = reachableNodes;
247248

248249
// force to remove all invalid items
249-
if (unreachableItems.Count > 0)
250+
if (possibleUnreachableItems.Count > 0)
250251
{
251-
JoinableTaskDependentData.RemoveUnreachableDependentItems(syncTask, unreachableItems, reachableNodes);
252+
JoinableTaskDependentData.RemoveUnreachableDependentItems(syncTask, possibleUnreachableItems, reachableNodes);
252253

253254
return true;
254255
}
@@ -419,6 +420,18 @@ internal static void RemoveDependency(IJoinableTaskDependent parentTaskOrCollect
419420
data.childDependentNodes.Remove(joinChild);
420421
data.RemoveDependingSynchronousTaskFromChild(joinChild);
421422
parentTaskOrCollection.OnDependencyRemoved(joinChild);
423+
424+
// A node with no out-going dependency chain cannot be a part of a circular dependency loop.
425+
// JoinableTaskCollection doesn't have a completion event, this logic makes sure it will be removed from a long runnning JTF.Run.
426+
if (data.HasNoChildDependentNode)
427+
{
428+
DependentSynchronousTask? existingTaskTracking = data.dependingSynchronousTaskTracking;
429+
while (existingTaskTracking is object)
430+
{
431+
existingTaskTracking.SynchronousTask.PotentialUnreachableDependents?.Remove(parentTaskOrCollection);
432+
existingTaskTracking = existingTaskTracking.Next;
433+
}
434+
}
422435
}
423436
else
424437
{
@@ -541,6 +554,8 @@ internal static void ComputeSelfAndDescendentOrJoinedJobsAndRemainTasks(IJoinabl
541554
/// <param name="reachableItems">All reachable items.</param>
542555
internal static void RemoveUnreachableDependentItems(JoinableTask syncTask, HashSet<IJoinableTaskDependent> unreachableItems, HashSet<IJoinableTaskDependent> reachableItems)
543556
{
557+
ThreadingEventSource.Instance.CircularJoinableTaskDependencyDetected(unreachableItems.Count, reachableItems.Count);
558+
544559
HashSet<IJoinableTaskDependent>? remainPlaceHold = null;
545560
foreach (IJoinableTaskDependent? unreachableItem in unreachableItems)
546561
{
@@ -615,7 +630,7 @@ internal bool HasMainThreadSynchronousTaskWaiting(IJoinableTaskDependent taskIte
615630
/// This is called when this task is completed.
616631
/// This method is expected to be used with the JTF lock.
617632
/// </summary>
618-
internal void OnTaskCompleted()
633+
internal void OnTaskCompleted(IJoinableTaskDependent thisDependentNode)
619634
{
620635
if (this.dependingSynchronousTaskTracking is object)
621636
{
@@ -628,6 +643,16 @@ internal void OnTaskCompleted()
628643
while (existingTaskTracking is object)
629644
{
630645
RemoveDependingSynchronousTaskFrom(childrenTasks, existingTaskTracking.SynchronousTask, false);
646+
647+
HashSet<IJoinableTaskDependent>? potentialUnreachableDependents = existingTaskTracking.SynchronousTask.PotentialUnreachableDependents;
648+
if (potentialUnreachableDependents != null)
649+
{
650+
if (potentialUnreachableDependents.Remove(thisDependentNode) && potentialUnreachableDependents.Count == 0)
651+
{
652+
existingTaskTracking.SynchronousTask.PotentialUnreachableDependents = null;
653+
}
654+
}
655+
631656
existingTaskTracking = existingTaskTracking.Next;
632657
}
633658
}
@@ -921,7 +946,8 @@ private static void RemoveDependingSynchronousTask(IJoinableTaskDependent taskOr
921946

922947
if (reachableNodes is null)
923948
{
924-
if (removed)
949+
// if a node doesn't have dependencies, it cannot be a part of a dependency circle.
950+
if (removed || taskOrCollection.GetJoinableTaskDependentData().HasNoChildDependentNode)
925951
{
926952
if (remainingDependentNodes is object)
927953
{

src/Microsoft.VisualStudio.Threading/ThreadingEventSource.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ internal sealed partial class ThreadingEventSource : EventSource
6666
/// </summary>
6767
private const int PostExecutionStopEvent = 16;
6868

69+
/// <summary>
70+
/// The event ID for the <see cref="CircularJoinableTaskDependencyDetected(int, int)"/>.
71+
/// </summary>
72+
private const int CircularJoinableTaskDependencyDetectedEvent = 17;
73+
6974
/// <summary>
7075
/// Logs an issued lock.
7176
/// </summary>
@@ -153,6 +158,17 @@ public void PostExecutionStop(int requestId)
153158
this.WriteEvent(PostExecutionStopEvent, requestId);
154159
}
155160

161+
/// <summary>
162+
/// Circular JoinableTask dependency detected.
163+
/// </summary>
164+
/// <param name="initUnreachableCount">Initial count of unreachable nodes.</param>
165+
/// <param name="reachableCount">The size of the connected dependency graph.</param>
166+
[Event(CircularJoinableTaskDependencyDetectedEvent, Level = EventLevel.Informational)]
167+
public void CircularJoinableTaskDependencyDetected(int initUnreachableCount, int reachableCount)
168+
{
169+
this.WriteEvent(CircularJoinableTaskDependencyDetectedEvent, initUnreachableCount, reachableCount);
170+
}
171+
156172
/// <summary>
157173
/// The names of constants in this class make up the middle term in
158174
/// the AsyncReaderWriterLock/LockRequest/Issued event name.

0 commit comments

Comments
 (0)