// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.VisualStudio.Threading
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
///
/// Methods to maintain dependencies between .
/// Those methods are expected to be called by or only to maintain relationship between them, and should not be called directly by other code.
///
internal static class JoinableTaskDependencyGraph
{
private static readonly HashSet EmptySet = new HashSet();
///
/// Gets a value indicating whether there is no child depenent item.
/// This method is expected to be used with the JTF lock.
///
internal static bool HasNoChildDependentNode(IJoinableTaskDependent taskItem)
{
Requires.NotNull(taskItem, nameof(taskItem));
Assumes.True(Monitor.IsEntered(taskItem.JoinableTaskContext.SyncContextLock));
return taskItem.GetJoinableTaskDependentData().HasNoChildDependentNode;
}
///
/// Checks whether a task or collection is a directly dependent of this item.
/// This method is expected to be used with the JTF lock.
///
internal static bool HasDirectDependency(IJoinableTaskDependent taskItem, IJoinableTaskDependent dependency)
{
Requires.NotNull(taskItem, nameof(taskItem));
Assumes.True(Monitor.IsEntered(taskItem.JoinableTaskContext.SyncContextLock));
return taskItem.GetJoinableTaskDependentData().HasDirectDependency(dependency);
}
///
/// Gets a value indicating whether the main thread is waiting for the task's completion.
///
internal static bool HasMainThreadSynchronousTaskWaiting(IJoinableTaskDependent taskItem)
{
Requires.NotNull(taskItem, nameof(taskItem));
using (taskItem.JoinableTaskContext.NoMessagePumpSynchronizationContext.Apply())
{
lock (taskItem.JoinableTaskContext.SyncContextLock)
{
return taskItem.GetJoinableTaskDependentData().HasMainThreadSynchronousTaskWaiting(taskItem);
}
}
}
///
/// Gets a likely value whether the main thread is blocked for the caller's completion.
///
internal static bool MaybeHasMainThreadSynchronousTaskWaiting(IJoinableTaskDependent taskItem)
{
return taskItem.GetJoinableTaskDependentData().MaybeHasMainThreadSynchronousTaskWaiting();
}
///
/// Adds a instance as one that is relevant to the async operation.
///
/// The current joinableTask or collection.
/// The to join as a child.
internal static JoinableTaskCollection.JoinRelease AddDependency(IJoinableTaskDependent taskItem, IJoinableTaskDependent joinChild)
{
Requires.NotNull(taskItem, nameof(taskItem));
return JoinableTaskDependentData.AddDependency(taskItem, joinChild);
}
///
/// Removes a instance as one that is no longer relevant to the async operation.
///
/// The current joinableTask or collection.
/// The to join as a child.
/// Ignore refCount, it is being used when the child task is completed.
internal static void RemoveDependency(IJoinableTaskDependent taskItem, IJoinableTaskDependent child, bool forceCleanup = false)
{
Requires.NotNull(taskItem, nameof(taskItem));
JoinableTaskDependentData.RemoveDependency(taskItem, child, forceCleanup);
}
///
/// Gets all dependent nodes registered in the dependency collection.
/// This method is expected to be used with the JTF lock.
///
internal static IEnumerable GetDirectDependentNodes(IJoinableTaskDependent taskItem)
{
Requires.NotNull(taskItem, nameof(taskItem));
Assumes.True(Monitor.IsEntered(taskItem.JoinableTaskContext.SyncContextLock));
return taskItem.GetJoinableTaskDependentData().GetDirectDependentNodes();
}
///
/// Check whether a task is being tracked in our tracking list.
///
internal static bool IsDependingSynchronousTask(IJoinableTaskDependent taskItem, JoinableTask syncTask)
{
Requires.NotNull(taskItem, nameof(taskItem));
return taskItem.GetJoinableTaskDependentData().IsDependingSynchronousTask(syncTask);
}
///
/// Calculate the collection of events we need trigger after we enqueue a request.
/// This method is expected to be used with the JTF lock.
///
/// The current joinableTask or collection.
/// True if we want to find tasks to process the main thread queue. Otherwise tasks to process the background queue.
/// The collection of synchronous tasks we need notify.
internal static IReadOnlyCollection GetDependingSynchronousTasks(IJoinableTaskDependent taskItem, bool forMainThread)
{
Requires.NotNull(taskItem, nameof(taskItem));
Assumes.True(Monitor.IsEntered(taskItem.JoinableTaskContext.SyncContextLock));
return taskItem.GetJoinableTaskDependentData().GetDependingSynchronousTasks(forMainThread);
}
///
/// Gets a snapshot of all joined tasks.
/// FOR DIAGNOSTICS COLLECTION ONLY.
/// This method is expected to be used with the JTF lock.
///
internal static IEnumerable GetAllDirectlyDependentJoinableTasks(IJoinableTaskDependent taskItem)
{
Requires.NotNull(taskItem, nameof(taskItem));
return JoinableTaskDependentData.GetAllDirectlyDependentJoinableTasks(taskItem);
}
///
/// Recursively adds this joinable and all its dependencies to the specified set, that are not yet completed.
///
internal static void AddSelfAndDescendentOrJoinedJobs(IJoinableTaskDependent taskItem, HashSet joinables)
{
Requires.NotNull(taskItem, nameof(taskItem));
JoinableTaskDependentData.AddSelfAndDescendentOrJoinedJobs(taskItem, joinables);
}
///
/// When the current dependent node is a synchronous task, this method is called before the thread is blocked to wait it to complete.
/// This adds the current task to the dependingSynchronousTaskTracking list of the task itself (which will propergate through its dependencies.)
/// After the task is finished, is called to revert this change.
/// This method is expected to be used with the JTF lock.
///
/// The current joinableTask or collection.
/// Return the JoinableTask which has already had pending requests to be handled.
/// The number of pending requests.
internal static void OnSynchronousTaskStartToBlockWaiting(JoinableTask taskItem, out JoinableTask? taskHasPendingRequests, out int pendingRequestsCount)
{
Requires.NotNull(taskItem, nameof(taskItem));
Assumes.True(Monitor.IsEntered(taskItem.Factory.Context.SyncContextLock));
JoinableTaskDependentData.OnSynchronousTaskStartToBlockWaiting(taskItem, out taskHasPendingRequests, out pendingRequestsCount);
}
///
/// When the current dependent node is a synchronous task, this method is called after the synchronous is completed, and the thread is no longer blocked.
/// This removes the current task from the dependingSynchronousTaskTracking list of the task itself (and propergate through its dependencies.)
/// It reverts the data structure change done in the .
///
internal static void OnSynchronousTaskEndToBlockWaiting(JoinableTask taskItem)
{
Requires.NotNull(taskItem, nameof(taskItem));
JoinableTaskDependentData.OnSynchronousTaskEndToBlockWaiting(taskItem);
}
///
/// Remove all synchronous tasks tracked by the this task.
/// This is called when this task is completed.
/// This method is expected to be used with the JTF lock.
///
internal static void OnTaskCompleted(IJoinableTaskDependent taskItem)
{
Requires.NotNull(taskItem, nameof(taskItem));
Assumes.True(Monitor.IsEntered(taskItem.JoinableTaskContext.SyncContextLock));
taskItem.GetJoinableTaskDependentData().OnTaskCompleted(taskItem);
}
///
/// Get all tasks inside the candidate sets tasks, which are depended by one or more task in the source tasks list.
///
/// A collection of JoinableTasks represents source tasks.
/// A collection of JoinableTasks which represents candidates.
/// A set of tasks matching the condition.
internal static HashSet GetDependentTasksFromCandidates(IEnumerable sourceTasks, IEnumerable candidateTasks)
{
Requires.NotNull(sourceTasks, nameof(sourceTasks));
Requires.NotNull(candidateTasks, nameof(candidateTasks));
var candidates = new HashSet(candidateTasks);
if (candidates.Count == 0)
{
return candidates;
}
var results = new HashSet();
var visited = new HashSet();
var queue = new Queue();
foreach (JoinableTask task in sourceTasks)
{
if (task is not null && visited.Add(task))
{
queue.Enqueue(task);
}
}
while (queue.Count > 0)
{
IJoinableTaskDependent startDepenentNode = queue.Dequeue();
if (startDepenentNode is JoinableTask startTask && candidates.Contains(startTask))
{
results.Add(startTask);
}
lock (startDepenentNode.JoinableTaskContext.SyncContextLock)
{
foreach (IJoinableTaskDependent? dependentItem in JoinableTaskDependencyGraph.GetDirectDependentNodes(startDepenentNode))
{
if (visited.Add(dependentItem))
{
queue.Enqueue(dependentItem);
}
}
}
}
return results;
}
///
/// Computes dependency graph to clean up all potential unreachable dependents items.
///
/// A thread blocking sychornizing task.
/// Returns all reachable nodes in the connected dependency graph, if unreachable dependency is found.
/// True if it removes any unreachable items.
internal static bool CleanUpPotentialUnreachableDependentItems(JoinableTask syncTask, [NotNullWhen(true)] out HashSet? allReachableNodes)
{
Requires.NotNull(syncTask, nameof(syncTask));
// a set of tasks may form a dependent loop, so it will make the reference count system
// not to work correctly when we try to remove the synchronous task.
// To get rid of those loops, if a task still tracks the synchronous task after reducing
// the reference count, we will calculate the entire reachable tree from the root. That will
// tell us the exactly tasks which need track the synchronous task, and we will clean up the rest.
HashSet? possibleUnreachableItems = syncTask.PotentialUnreachableDependents;
if (possibleUnreachableItems is object && possibleUnreachableItems.Count > 0)
{
var reachableNodes = new HashSet();
IJoinableTaskDependent syncTaskItem = syncTask;
JoinableTaskDependentData.ComputeSelfAndDescendentOrJoinedJobsAndRemainTasks(syncTaskItem, reachableNodes, possibleUnreachableItems);
allReachableNodes = reachableNodes;
// force to remove all invalid items
if (possibleUnreachableItems.Count > 0)
{
JoinableTaskDependentData.RemoveUnreachableDependentItems(syncTask, possibleUnreachableItems, reachableNodes);
possibleUnreachableItems.Clear();
return true;
}
}
allReachableNodes = null;
return false;
}
///
/// Force to clean up all unreachable dependent item, so they are not marked to block the syncTask.
///
/// The thread blocking task.
/// Unreachable dependent items.
/// All reachable items.
internal static void RemoveUnreachableDependentItems(JoinableTask syncTask, HashSet unreachableItems, HashSet reachableItems)
{
Requires.NotNull(syncTask, nameof(syncTask));
Requires.NotNull(unreachableItems, nameof(unreachableItems));
Requires.NotNull(reachableItems, nameof(reachableItems));
JoinableTaskDependentData.RemoveUnreachableDependentItems(syncTask, unreachableItems, reachableItems);
}
///
/// Preserve data for the JoinableTask dependency tree. It is holded inside either a or a .
/// Do not call methods/properties directly anywhere out of .
///
internal struct JoinableTaskDependentData
{
///
/// A map of jobs that we should be willing to dequeue from when we control the UI thread, and a ref count. Lazily constructed.
///
///
/// When the value in an entry is decremented to 0, the entry is removed from the map.
///
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private Dictionary childDependentNodes;
///
/// The head of a singly linked list of records to track which task may process events of this task.
/// This list should contain only tasks which need be completed synchronously, and depends on this task.
///
private DependentSynchronousTask? dependingSynchronousTaskTracking;
///
/// Gets a value indicating whether the is empty.
///
internal bool HasNoChildDependentNode => this.childDependentNodes is null || this.childDependentNodes.Count == 0;
///
/// Gets a snapshot of all joined tasks.
/// FOR DIAGNOSTICS COLLECTION ONLY.
/// This method is expected to be used with the JTF lock.
///
/// The current joinableTask or collection contains this data.
internal static IEnumerable GetAllDirectlyDependentJoinableTasks(IJoinableTaskDependent taskOrCollection)
{
Requires.NotNull(taskOrCollection, nameof(taskOrCollection));
Assumes.True(Monitor.IsEntered(taskOrCollection.JoinableTaskContext.SyncContextLock));
if (taskOrCollection.GetJoinableTaskDependentData().childDependentNodes is null)
{
return Enumerable.Empty();
}
var allTasks = new HashSet();
AddSelfAndDescendentOrJoinedJobs(taskOrCollection, allTasks);
return allTasks;
}
///
/// Adds a instance as one that is relevant to the async operation.
///
/// The current joinableTask or collection contains to add a dependency.
/// The to join as a child.
internal static JoinableTaskCollection.JoinRelease AddDependency(IJoinableTaskDependent parentTaskOrCollection, IJoinableTaskDependent joinChild)
{
Requires.NotNull(parentTaskOrCollection, nameof(parentTaskOrCollection));
Requires.NotNull(joinChild, nameof(joinChild));
if (parentTaskOrCollection == joinChild)
{
// Joining oneself would be pointless.
return default(JoinableTaskCollection.JoinRelease);
}
using (parentTaskOrCollection.JoinableTaskContext.NoMessagePumpSynchronizationContext.Apply())
{
List? eventsNeedNotify = null;
lock (parentTaskOrCollection.JoinableTaskContext.SyncContextLock)
{
var joinableTask = joinChild as JoinableTask;
if (joinableTask?.IsFullyCompleted == true)
{
return default(JoinableTaskCollection.JoinRelease);
}
ref JoinableTaskDependentData data = ref parentTaskOrCollection.GetJoinableTaskDependentData();
if (data.childDependentNodes is null)
{
data.childDependentNodes = new Dictionary(capacity: 2);
}
if (data.childDependentNodes.TryGetValue(joinChild, out int refCount) && !parentTaskOrCollection.NeedRefCountChildDependencies)
{
return default(JoinableTaskCollection.JoinRelease);
}
data.childDependentNodes[joinChild] = ++refCount;
if (refCount == 1)
{
// This constitutes a significant change, so we should apply synchronous task tracking to the new child.
joinChild.OnAddedToDependency(parentTaskOrCollection);
IReadOnlyCollection? tasksNeedNotify = AddDependingSynchronousTaskToChild(parentTaskOrCollection, joinChild);
if (tasksNeedNotify.Count > 0)
{
eventsNeedNotify = new List(tasksNeedNotify.Count);
foreach (PendingNotification taskToNotify in tasksNeedNotify)
{
AsyncManualResetEvent? notifyEvent = taskToNotify.SynchronousTask.RegisterPendingEventsForSynchrousTask(taskToNotify.TaskHasPendingMessages, taskToNotify.NewPendingMessagesCount);
if (notifyEvent is object)
{
eventsNeedNotify.Add(notifyEvent);
}
}
}
parentTaskOrCollection.OnDependencyAdded(joinChild);
}
}
// We explicitly do this outside our lock.
if (eventsNeedNotify is object)
{
foreach (AsyncManualResetEvent? queueEvent in eventsNeedNotify)
{
queueEvent.PulseAll();
}
}
return new JoinableTaskCollection.JoinRelease(parentTaskOrCollection, joinChild);
}
}
///
/// Removes a instance as one that is no longer relevant to the async operation.
///
/// The current joinableTask or collection contains to remove a dependency.
/// The to join as a child.
/// Ignore refCount, it is being used when the child task is completed.
internal static void RemoveDependency(IJoinableTaskDependent parentTaskOrCollection, IJoinableTaskDependent joinChild, bool forceCleanup)
{
Requires.NotNull(parentTaskOrCollection, nameof(parentTaskOrCollection));
Requires.NotNull(joinChild, nameof(joinChild));
using (parentTaskOrCollection.JoinableTaskContext.NoMessagePumpSynchronizationContext.Apply())
{
ref JoinableTaskDependentData data = ref parentTaskOrCollection.GetJoinableTaskDependentData();
lock (parentTaskOrCollection.JoinableTaskContext.SyncContextLock)
{
if (data.childDependentNodes is object && data.childDependentNodes.TryGetValue(joinChild, out int refCount))
{
if (refCount == 1 || forceCleanup)
{
joinChild.OnRemovedFromDependency(parentTaskOrCollection);
data.childDependentNodes.Remove(joinChild);
data.RemoveDependingSynchronousTaskFromChild(joinChild);
parentTaskOrCollection.OnDependencyRemoved(joinChild);
// A node with no out-going dependency chain cannot be a part of a circular dependency loop.
// JoinableTaskCollection doesn't have a completion event, this logic makes sure it will be removed from a long runnning JTF.Run.
if (data.HasNoChildDependentNode)
{
DependentSynchronousTask? existingTaskTracking = data.dependingSynchronousTaskTracking;
while (existingTaskTracking is object)
{
existingTaskTracking.SynchronousTask.PotentialUnreachableDependents?.Remove(parentTaskOrCollection);
existingTaskTracking = existingTaskTracking.Next;
}
}
}
else
{
data.childDependentNodes[joinChild] = --refCount;
}
}
}
}
}
///
/// Recursively adds this joinable and all its dependencies to the specified set, that are not yet completed.
///
/// The current joinableTask or collection contains this data.
/// A collection to hold found.
internal static void AddSelfAndDescendentOrJoinedJobs(IJoinableTaskDependent taskOrCollection, HashSet joinables)
{
Requires.NotNull(taskOrCollection, nameof(taskOrCollection));
Requires.NotNull(joinables, nameof(joinables));
if (taskOrCollection is JoinableTask thisJoinableTask)
{
if (thisJoinableTask.IsCompleteRequested)
{
if (!thisJoinableTask.IsFullyCompleted)
{
joinables.Add(thisJoinableTask);
}
return;
}
if (!joinables.Add(thisJoinableTask))
{
return;
}
}
Dictionary? childDependentNodes = taskOrCollection.GetJoinableTaskDependentData().childDependentNodes;
if (childDependentNodes is object)
{
foreach (KeyValuePair item in childDependentNodes)
{
AddSelfAndDescendentOrJoinedJobs(item.Key, joinables);
}
}
}
///
/// When the current dependent node is a synchronous task, this method is called before the thread is blocked to wait it to complete.
/// This adds the current task to the of the task itself (which will propergate through its dependencies.)
/// After the task is finished, is called to revert this change.
/// This method is expected to be used with the JTF lock.
///
/// The synchronized joinableTask.
/// Return the JoinableTask which has already had pending requests to be handled.
/// The number of pending requests.
internal static void OnSynchronousTaskStartToBlockWaiting(JoinableTask syncTask, out JoinableTask? taskHasPendingRequests, out int pendingRequestsCount)
{
Requires.NotNull(syncTask, nameof(syncTask));
pendingRequestsCount = 0;
taskHasPendingRequests = AddDependingSynchronousTask(syncTask, syncTask, ref pendingRequestsCount);
}
///
/// When the current dependent node is a synchronous task, this method is called after the synchronous is completed, and the thread is no longer blocked.
/// This removes the current task from the of the task itself (and propergate through its dependencies.)
/// It reverts the data structure change done in the .
///
/// The synchronized joinableTask.
internal static void OnSynchronousTaskEndToBlockWaiting(JoinableTask syncTask)
{
Requires.NotNull(syncTask, nameof(syncTask));
using (syncTask.Factory.Context.NoMessagePumpSynchronizationContext.Apply())
{
lock (syncTask.Factory.Context.SyncContextLock)
{
// Remove itself from the tracking list, after the task is completed.
IJoinableTaskDependent syncTaskItem = syncTask;
if (syncTaskItem.GetJoinableTaskDependentData().dependingSynchronousTaskTracking is object)
{
RemoveDependingSynchronousTask(syncTask, syncTask, force: true);
}
if (syncTask.PotentialUnreachableDependents is object && syncTask.PotentialUnreachableDependents.Count > 0)
{
RemoveUnreachableDependentItems(syncTask, syncTask.PotentialUnreachableDependents, EmptySet);
syncTask.PotentialUnreachableDependents = null;
}
}
}
}
///
/// Compute all reachable nodes from a synchronous task. Because we use the result to clean up invalid
/// items from the remain task, we will remove valid task from the collection, and stop immediately if nothing is left.
///
/// The current joinableTask or collection owns the data.
/// All reachable dependency nodes. This is not a completed list, if there is no remain node.
/// Remain dependency nodes we want to check. After the execution, it will retain non-reachable nodes.
internal static void ComputeSelfAndDescendentOrJoinedJobsAndRemainTasks(IJoinableTaskDependent taskOrCollection, HashSet reachableNodes, HashSet remainNodes)
{
Requires.NotNull(taskOrCollection, nameof(taskOrCollection));
Requires.NotNull(remainNodes, nameof(remainNodes));
Requires.NotNull(reachableNodes, nameof(reachableNodes));
if ((taskOrCollection as JoinableTask)?.IsFullyCompleted != true)
{
if (reachableNodes.Add(taskOrCollection))
{
if (remainNodes.Remove(taskOrCollection) && remainNodes.Count == 0)
{
// no remain task left, quit the loop earlier
return;
}
if ((taskOrCollection as JoinableTask)?.IsCompleteRequested == true)
{
return;
}
Dictionary? dependencies = taskOrCollection.GetJoinableTaskDependentData().childDependentNodes;
if (dependencies is object)
{
foreach (KeyValuePair item in dependencies)
{
ComputeSelfAndDescendentOrJoinedJobsAndRemainTasks(item.Key, reachableNodes, remainNodes);
if (remainNodes.Count == 0)
{
return;
}
}
}
}
}
}
///
/// Force to clean up all unreachable dependent item, so they are not marked to block the syncTask.
///
/// The thread blocking task.
/// Unreachable dependent items.
/// All reachable items.
internal static void RemoveUnreachableDependentItems(JoinableTask syncTask, HashSet unreachableItems, HashSet reachableItemsReadOnlySet)
{
ThreadingEventSource.Instance.CircularJoinableTaskDependencyDetected(unreachableItems.Count, reachableItemsReadOnlySet.Count);
HashSet? remainPlaceHold = null;
foreach (IJoinableTaskDependent? unreachableItem in unreachableItems)
{
RemoveDependingSynchronousTask(unreachableItem, syncTask, reachableItemsReadOnlySet, ref remainPlaceHold);
}
}
///
/// Gets all dependent nodes registered in the
/// This method is expected to be used with the JTF lock.
///
internal IEnumerable GetDirectDependentNodes()
{
if (this.childDependentNodes is null)
{
return Enumerable.Empty();
}
return this.childDependentNodes.Keys;
}
///
/// Checks whether a dependent node is inside .
/// This method is expected to be used with the JTF lock.
///
internal bool HasDirectDependency(IJoinableTaskDependent dependency)
{
if (this.childDependentNodes is null)
{
return false;
}
return this.childDependentNodes.ContainsKey(dependency);
}
///
/// Gets a value indicating whether the main thread is waiting for the task's completion
/// This method is expected to be used with the JTF lock.
///
internal bool HasMainThreadSynchronousTaskWaiting(IJoinableTaskDependent taskItem)
{
DependentSynchronousTask? existingTaskTracking = this.dependingSynchronousTaskTracking;
while (existingTaskTracking is object)
{
DependentSynchronousTask? nextTrackingTask = existingTaskTracking.Next;
if ((existingTaskTracking.SynchronousTask.State & JoinableTask.JoinableTaskFlags.SynchronouslyBlockingMainThread) == JoinableTask.JoinableTaskFlags.SynchronouslyBlockingMainThread)
{
if (existingTaskTracking.SynchronousTask.HasPotentialUnreachableDependents)
{
// This might remove the current tracking item from the linked list, so we capture next node first.
if (!CleanUpPotentialUnreachableDependentItems(existingTaskTracking.SynchronousTask, out HashSet? allReachableNodes) ||
allReachableNodes.Contains(taskItem))
{
// this task is still a dependenting task
return true;
}
}
else
{
return true;
}
}
existingTaskTracking = nextTrackingTask;
}
return false;
}
///
/// Gets a likely value whether the main thread is blocked for the caller's completion.
///
internal bool MaybeHasMainThreadSynchronousTaskWaiting()
{
DependentSynchronousTask? existingTaskTracking = this.dependingSynchronousTaskTracking;
while (existingTaskTracking is object)
{
if ((existingTaskTracking.SynchronousTask.State & JoinableTask.JoinableTaskFlags.SynchronouslyBlockingMainThread) == JoinableTask.JoinableTaskFlags.SynchronouslyBlockingMainThread)
{
return true;
}
existingTaskTracking = existingTaskTracking.Next;
}
return false;
}
///
/// Remove all synchronous tasks tracked by the this task.
/// This is called when this task is completed.
/// This method is expected to be used with the JTF lock.
///
internal void OnTaskCompleted(IJoinableTaskDependent thisDependentNode)
{
if (this.dependingSynchronousTaskTracking is object)
{
DependentSynchronousTask? existingTaskTracking = this.dependingSynchronousTaskTracking;
this.dependingSynchronousTaskTracking = null;
if (this.childDependentNodes is object)
{
Dictionary.KeyCollection? childrenTasks = this.childDependentNodes.Keys;
while (existingTaskTracking is object)
{
RemoveDependingSynchronousTaskFrom(childrenTasks, existingTaskTracking.SynchronousTask, force: existingTaskTracking.SynchronousTask == thisDependentNode);
HashSet? potentialUnreachableDependents = existingTaskTracking.SynchronousTask.PotentialUnreachableDependents;
if (potentialUnreachableDependents is object && potentialUnreachableDependents.Count > 0)
{
potentialUnreachableDependents.Remove(thisDependentNode);
}
existingTaskTracking = existingTaskTracking.Next;
}
}
}
}
///
/// Check whether a task is being tracked in our tracking list.
///
internal bool IsDependingSynchronousTask(JoinableTask syncTask)
{
DependentSynchronousTask? existingTaskTracking = this.dependingSynchronousTaskTracking;
while (existingTaskTracking is object)
{
if (existingTaskTracking.SynchronousTask == syncTask)
{
return true;
}
existingTaskTracking = existingTaskTracking.Next;
}
return false;
}
///
/// Calculate the collection of events we need trigger after we enqueue a request.
/// This method is expected to be used with the JTF lock.
///
/// True if we want to find tasks to process the main thread queue. Otherwise tasks to process the background queue.
/// The collection of synchronous tasks we need notify.
internal IReadOnlyCollection GetDependingSynchronousTasks(bool forMainThread)
{
int count = this.CountOfDependingSynchronousTasks();
if (count == 0)
{
return Array.Empty();
}
var tasksNeedNotify = new List(count);
DependentSynchronousTask? existingTaskTracking = this.dependingSynchronousTaskTracking;
while (existingTaskTracking is object)
{
JoinableTask? syncTask = existingTaskTracking.SynchronousTask;
bool syncTaskInOnMainThread = (syncTask.State & JoinableTask.JoinableTaskFlags.SynchronouslyBlockingMainThread) == JoinableTask.JoinableTaskFlags.SynchronouslyBlockingMainThread;
if (forMainThread == syncTaskInOnMainThread)
{
// Only synchronous tasks are in the list, so we don't need do further check for the CompletingSynchronously flag
tasksNeedNotify.Add(syncTask);
}
existingTaskTracking = existingTaskTracking.Next;
}
return tasksNeedNotify;
}
///
/// Applies all synchronous tasks tracked by this task to a new child/dependent task.
///
/// The current joinableTask or collection owns the data.
/// The new child task.
/// Pairs of synchronous tasks we need notify and the event source triggering it, plus the number of pending events.
private static IReadOnlyCollection AddDependingSynchronousTaskToChild(IJoinableTaskDependent dependentNode, IJoinableTaskDependent child)
{
Requires.NotNull(dependentNode, nameof(dependentNode));
Requires.NotNull(child, nameof(child));
Assumes.True(Monitor.IsEntered(dependentNode.JoinableTaskContext.SyncContextLock));
ref JoinableTaskDependentData data = ref dependentNode.GetJoinableTaskDependentData();
int count = data.CountOfDependingSynchronousTasks();
if (count == 0)
{
return Array.Empty();
}
var tasksNeedNotify = new List(count);
DependentSynchronousTask? existingTaskTracking = data.dependingSynchronousTaskTracking;
while (existingTaskTracking is object)
{
int totalEventNumber = 0;
JoinableTask? eventTriggeringTask = AddDependingSynchronousTask(child, existingTaskTracking.SynchronousTask, ref totalEventNumber);
if (eventTriggeringTask is object)
{
tasksNeedNotify.Add(new PendingNotification(existingTaskTracking.SynchronousTask, eventTriggeringTask, totalEventNumber));
}
existingTaskTracking = existingTaskTracking.Next;
}
return tasksNeedNotify;
}
///
/// Tracks a new synchronous task for this task.
/// A synchronous task is a task blocking a thread and waits it to be completed. We may want the blocking thread
/// to process events from this task.
///
/// The current joinableTask or collection.
/// The synchronous task.
/// The total events need be processed.
/// The task causes us to trigger the event of the synchronous task, so it can process new events. Null means we don't need trigger any event.
private static JoinableTask? AddDependingSynchronousTask(IJoinableTaskDependent taskOrCollection, JoinableTask synchronousTask, ref int totalEventsPending)
{
Requires.NotNull(taskOrCollection, nameof(taskOrCollection));
Requires.NotNull(synchronousTask, nameof(synchronousTask));
Assumes.True(Monitor.IsEntered(taskOrCollection.JoinableTaskContext.SyncContextLock));
JoinableTask? thisJoinableTask = taskOrCollection as JoinableTask;
if (thisJoinableTask is object)
{
if (thisJoinableTask.IsCompleteRequested)
{
if (!thisJoinableTask.IsFullyCompleted)
{
// A completed task might still have pending items in the queue.
int pendingCount = thisJoinableTask.GetPendingEventCountForSynchronousTask(synchronousTask);
if (pendingCount > 0)
{
totalEventsPending += pendingCount;
return thisJoinableTask;
}
}
return null;
}
}
ref JoinableTaskDependentData data = ref taskOrCollection.GetJoinableTaskDependentData();
DependentSynchronousTask? existingTaskTracking = data.dependingSynchronousTaskTracking;
while (existingTaskTracking is object)
{
if (existingTaskTracking.SynchronousTask == synchronousTask)
{
existingTaskTracking.ReferenceCount++;
return null;
}
existingTaskTracking = existingTaskTracking.Next;
}
JoinableTask? eventTriggeringTask = null;
if (thisJoinableTask is object)
{
int pendingItemCount = thisJoinableTask.GetPendingEventCountForSynchronousTask(synchronousTask);
if (pendingItemCount > 0)
{
totalEventsPending += pendingItemCount;
eventTriggeringTask = thisJoinableTask;
}
}
// For a new synchronous task, we need apply it to our child tasks.
var newTaskTracking = new DependentSynchronousTask(synchronousTask)
{
Next = data.dependingSynchronousTaskTracking,
};
Thread.MemoryBarrier();
data.dependingSynchronousTaskTracking = newTaskTracking;
if (data.childDependentNodes is object)
{
foreach (KeyValuePair item in data.childDependentNodes)
{
JoinableTask? childTiggeringTask = AddDependingSynchronousTask(item.Key, synchronousTask, ref totalEventsPending);
if (eventTriggeringTask is null)
{
eventTriggeringTask = childTiggeringTask;
}
}
}
return eventTriggeringTask;
}
///
/// Remove a synchronous task from the tracking list.
///
/// The current joinableTask or collection.
/// The synchronous task.
/// We always remove it from the tracking list if it is true. Otherwise, we keep tracking the reference count.
private static void RemoveDependingSynchronousTask(IJoinableTaskDependent taskOrCollection, JoinableTask syncTask, bool force = false)
{
Requires.NotNull(taskOrCollection, nameof(taskOrCollection));
Requires.NotNull(syncTask, nameof(syncTask));
Assumes.True(Monitor.IsEntered(taskOrCollection.JoinableTaskContext.SyncContextLock));
RemoveDependingSynchronousTaskFrom(new IJoinableTaskDependent[] { taskOrCollection }, syncTask, force);
}
///
/// Remove a synchronous task from the tracking list of a list of tasks.
///
/// A list of tasks we need update the tracking list.
/// The synchronous task we want to remove.
/// We always remove it from the tracking list if it is true. Otherwise, we keep tracking the reference count.
private static void RemoveDependingSynchronousTaskFrom(IReadOnlyCollection tasks, JoinableTask syncTask, bool force)
{
Requires.NotNull(tasks, nameof(tasks));
Requires.NotNull(syncTask, nameof(syncTask));
HashSet? emptySetOrNull = force ? EmptySet : null;
HashSet? remainNodes = syncTask.PotentialUnreachableDependents;
foreach (IJoinableTaskDependent? task in tasks)
{
RemoveDependingSynchronousTask(task, syncTask, reachableNodesReadOnlySet: emptySetOrNull, ref remainNodes);
}
if (remainNodes is object && remainNodes.Count > 0)
{
if (force)
{
Assumes.NotNull(emptySetOrNull);
Assumes.True(emptySetOrNull.Count == 0);
RemoveUnreachableDependentItems(syncTask, remainNodes, reachableItemsReadOnlySet: emptySetOrNull);
syncTask.PotentialUnreachableDependents = null;
}
else if (syncTask.PotentialUnreachableDependents != remainNodes)
{
// a set of tasks may form a dependent loop, so it will make the reference count system
// not to work correctly when we try to remove the synchronous task.
// It will require full dependency scanning to clean them up, which is quite expensive,
// so we keep tracking them, and clean them up when it becomes essential.
syncTask.PotentialUnreachableDependents = remainNodes;
}
}
}
///
/// Remove a synchronous task from the tracking list of this task.
///
/// The current joinableTask or collection.
/// The synchronous task.
///
/// If it is not null, it will contain all dependency nodes which can track the synchronous task. We will ignore reference count in that case.
///
/// This will retain the tasks which still tracks the synchronous task.
private static void RemoveDependingSynchronousTask(IJoinableTaskDependent taskOrCollection, JoinableTask task, HashSet? reachableNodesReadOnlySet, ref HashSet? remainingDependentNodes)
{
Requires.NotNull(taskOrCollection, nameof(taskOrCollection));
Requires.NotNull(task, nameof(task));
ref JoinableTaskDependentData data = ref taskOrCollection.GetJoinableTaskDependentData();
DependentSynchronousTask? previousTaskTracking = null;
DependentSynchronousTask? currentTaskTracking = data.dependingSynchronousTaskTracking;
bool removed = false;
while (currentTaskTracking is object)
{
if (currentTaskTracking.SynchronousTask == task)
{
if (--currentTaskTracking.ReferenceCount > 0)
{
if (reachableNodesReadOnlySet is object)
{
if (!reachableNodesReadOnlySet.Contains(taskOrCollection))
{
currentTaskTracking.ReferenceCount = 0;
}
}
}
if (currentTaskTracking.ReferenceCount == 0)
{
removed = true;
if (previousTaskTracking is object)
{
previousTaskTracking.Next = currentTaskTracking.Next;
}
else
{
data.dependingSynchronousTaskTracking = currentTaskTracking.Next;
}
}
if (reachableNodesReadOnlySet is null)
{
// if a node doesn't have dependencies, it cannot be a part of a dependency circle.
if (removed || taskOrCollection.GetJoinableTaskDependentData().HasNoChildDependentNode)
{
if (remainingDependentNodes is object)
{
remainingDependentNodes.Remove(taskOrCollection);
}
}
else
{
if (remainingDependentNodes is null)
{
remainingDependentNodes = new HashSet();
}
remainingDependentNodes.Add(taskOrCollection);
}
}
break;
}
previousTaskTracking = currentTaskTracking;
currentTaskTracking = currentTaskTracking.Next;
}
if (removed && data.childDependentNodes is object)
{
foreach (KeyValuePair item in data.childDependentNodes)
{
RemoveDependingSynchronousTask(item.Key, task, reachableNodesReadOnlySet, ref remainingDependentNodes);
}
}
}
///
/// Get how many number of synchronous tasks in our tracking list.
///
private int CountOfDependingSynchronousTasks()
{
int count = 0;
DependentSynchronousTask? existingTaskTracking = this.dependingSynchronousTaskTracking;
while (existingTaskTracking is object)
{
count++;
existingTaskTracking = existingTaskTracking.Next;
}
return count;
}
///
/// Removes all synchronous tasks we applies to a dependent task, after the relationship is removed.
///
/// The original dependent task.
private void RemoveDependingSynchronousTaskFromChild(IJoinableTaskDependent child)
{
Requires.NotNull(child, nameof(child));
DependentSynchronousTask? existingTaskTracking = this.dependingSynchronousTaskTracking;
while (existingTaskTracking is object)
{
RemoveDependingSynchronousTask(child, existingTaskTracking.SynchronousTask);
existingTaskTracking = existingTaskTracking.Next;
}
}
///
/// The record of a pending notification we need send to the synchronous task that we have some new messages to process.
///
private readonly struct PendingNotification
{
internal PendingNotification(JoinableTask synchronousTask, JoinableTask taskHasPendingMessages, int newPendingMessagesCount)
{
Requires.NotNull(synchronousTask, nameof(synchronousTask));
Requires.NotNull(taskHasPendingMessages, nameof(taskHasPendingMessages));
this.SynchronousTask = synchronousTask;
this.TaskHasPendingMessages = taskHasPendingMessages;
this.NewPendingMessagesCount = newPendingMessagesCount;
}
///
/// Gets the synchronous task which need process new messages.
///
internal JoinableTask SynchronousTask { get; }
///
/// Gets one JoinableTask which may have pending messages. We may have multiple new JoinableTasks which contains pending messages.
/// This is just one of them. It gives the synchronous task a way to start quickly without searching all messages.
///
internal JoinableTask TaskHasPendingMessages { get; }
///
/// Gets the total number of new pending messages. The real number could be less than that, but should not be more than that.
///
internal int NewPendingMessagesCount { get; }
}
///
/// A single linked list to maintain synchronous JoinableTask depends on the current task,
/// which may process the queue of the current task.
///
private class DependentSynchronousTask
{
internal DependentSynchronousTask(JoinableTask task)
{
this.SynchronousTask = task;
this.ReferenceCount = 1;
}
///
/// Gets or sets the chain of the single linked list.
///
internal DependentSynchronousTask? Next { get; set; }
///
/// Gets the synchronous task.
///
internal JoinableTask SynchronousTask { get; }
///
/// Gets or sets the reference count. We remove the item from the list, if it reaches 0.
///
internal int ReferenceCount { get; set; }
}
}
}
}