in Forge.TreeWalker/src/TreeWalkerSession.cs [298:461]
public async Task<string> WalkTree(string treeNodeKey)
{
this.Status = "Running";
// Start a single task to walk the tree to completion.
// This task will only start new tasks to execute actions.
Task walkTreeTask = Task.Run(async () =>
{
string current = treeNodeKey;
string next;
// Starting with the given tree node key, visit the returned children nodes until you hit a node with no matching children.
// Call the callbacks before/after visiting each node.
do
{
await this.CommitCurrentTreeNode(current, this.Schema.Tree[current]).ConfigureAwait(false);
if (this.walkTreeCts.Token.IsCancellationRequested)
{
this.Status = "Cancelled";
this.walkTreeCts.Token.ThrowIfCancellationRequested();
}
TreeNodeContext treeNodeContext = null;
if (this.Parameters.CallbacksV2 != null)
{
// If applicable, use the new CallbacksV2 with ITreeWalkerCallbacksV2
treeNodeContext = new TreeNodeContext(
this.Parameters.SessionId,
current,
await this.EvaluateDynamicProperty(this.Schema.Tree[current].Properties, null),
this.Parameters.UserContext,
this.walkTreeCts.Token,
this.Parameters.TreeName,
this.Parameters.RootSessionId,
currentNodeSkipActionContext:null
);
await this.Parameters.CallbacksV2.BeforeVisitNode(treeNodeContext).ConfigureAwait(false);
this.currentNodeSkipActionContext = treeNodeContext.CurrentNodeSkipActionContext;
}
else
{
// Follow the previous Callbacks with ITreeWalkerCallbacks.
await this.Parameters.Callbacks.BeforeVisitNode(
this.Parameters.SessionId,
current,
await this.EvaluateDynamicProperty(this.Schema.Tree[current].Properties, null),
this.Parameters.UserContext,
this.Parameters.TreeName,
this.Parameters.RootSessionId,
this.walkTreeCts.Token).ConfigureAwait(false);
}
try
{
// Exceptions are thrown here if VisitNode hit a timeout, was cancelled, or failed.
next = await this.VisitNode(current).ConfigureAwait(false);
}
finally
{
// Always call AfterVisitNode, even if VisitNode threw exception.
if (this.Parameters.CallbacksV2 != null)
{
// If applicable, use the new CallbacksV2 with ITreeWalkerCallbacksV2
// Recreating the TreeNodeContext here to reevaluate TreeNode.Properties.
treeNodeContext = new TreeNodeContext(
this.Parameters.SessionId,
current,
await this.EvaluateDynamicProperty(this.Schema.Tree[current].Properties, null),
this.Parameters.UserContext,
this.walkTreeCts.Token,
this.Parameters.TreeName,
this.Parameters.RootSessionId,
this.currentNodeSkipActionContext
);
await this.Parameters.CallbacksV2.AfterVisitNode(treeNodeContext).ConfigureAwait(false);
}
else
{
await this.Parameters.Callbacks.AfterVisitNode(
this.Parameters.SessionId,
current,
await this.EvaluateDynamicProperty(this.Schema.Tree[current].Properties, null),
this.Parameters.UserContext,
this.Parameters.TreeName,
this.Parameters.RootSessionId,
this.walkTreeCts.Token).ConfigureAwait(false);
}
// Clear the context of SkipAction before visiting next node, because it is only valid locally for this current tree node.
this.currentNodeSkipActionContext = null;
}
current = next;
} while (!string.IsNullOrWhiteSpace(current));
if (string.IsNullOrWhiteSpace(current))
{
// Null child means the tree ran to completion.
this.Status = "RanToCompletion";
}
}, this.walkTreeCts.Token);
try
{
// Exceptions are thrown here if tree walker hit a timeout, was cancelled, or failed.
// Let's update the Status according to the exception thrown before rethrowing the exception.
await walkTreeTask;
}
catch (TaskCanceledException)
{
// Tree walker was cancelled before calling WalkTree.
this.Status = "CancelledBeforeExecution";
}
catch (OperationCanceledException)
{
// Tree walker was cancelled after calling WalkTree (no timeouts hit).
this.Status = "Cancelled";
}
catch (ActionTimeoutException)
{
// An action-level timeout was hit.
this.Status = "TimeoutOnAction";
}
catch (TimeoutException)
{
// A node-level timeout was hit.
this.Status = "TimeoutOnNode";
}
catch (NoChildMatchedException)
{
// ChildSelector couldn't select any child.
this.Status = "RanToCompletion_NoChildMatched";
}
catch (EvaluateDynamicPropertyException)
{
this.Status = "Failed_EvaluateDynamicProperty";
}
catch (Exception)
{
// TODO: Consider checking the exception for specific Data entry and setting Status to that.
// This would allow callbacks such as BeforeVisitNode to throw exceptions and control the status instead of it being Failed.
// Unexpected exception was thrown in actions, callbacks, or elsewhere, resulting in failure and cancellation.
this.Status = "Failed";
}
finally
{
this.CancelWalkTree();
}
try
{
// Exceptions are thrown here if tree walker hit a timeout, was cancelled, or failed.
await walkTreeTask;
}
catch (NoChildMatchedException)
{
// For now, suppressing this exception so that its treated as successful end stage.
}
return this.Status;
}