public async Task WalkTree()

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;
        }