@@ -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 {
0 commit comments