internal async Task ExecuteAction()

in Forge.TreeWalker/src/TreeWalkerSession.cs [865:952]


        internal async Task ExecuteAction(
            string treeNodeKey,
            string treeActionKey,
            TreeAction treeAction,
            ActionDefinition actionDefinition,
            Task actionTimeoutTask,
            CancellationToken token)
        {
            // Set up a linked cancellation token to trigger on timeout if ContinuationOnTimeout is set.
            // This ensures the runActionTask gets canceled when Forge timeout is hit.
            CancellationTokenSource actionCts = CancellationTokenSource.CreateLinkedTokenSource(token);
            token = treeAction.ContinuationOnTimeout ? actionCts.Token : token;

            // Evaluate the dynamic properties that are used by the actionTask.
            ActionContext actionContext = new ActionContext(
                this.Parameters.SessionId,
                treeNodeKey,
                treeActionKey,
                treeAction.Action,
                await this.EvaluateDynamicProperty(treeAction.Input, actionDefinition.InputType).ConfigureAwait(false),
                await this.EvaluateDynamicProperty(treeAction.Properties, null).ConfigureAwait(false),
                this.Parameters.UserContext,
                token,
                this.Parameters.ForgeState,
                this.Parameters.TreeName,
                this.Parameters.RootSessionId
            );

            // Instantiate the BaseAction-derived ActionType class and invoke the RunAction method on it.
            object actionObject;
            if (actionDefinition.ActionType == typeof(SubroutineAction))
            {
                // Special initializer is used for the native SubroutineAction.
                actionObject = Activator.CreateInstance(actionDefinition.ActionType, this.Parameters);
            }
            else
            {
                actionObject = Activator.CreateInstance(actionDefinition.ActionType);
            }

            MethodInfo method = typeof(BaseAction).GetMethod("RunAction");
            Task<ActionResponse> runActionTask = (Task<ActionResponse>) method.Invoke(actionObject, new object[] { actionContext });

            // Await for the first completed task between our runActionTask and the timeout task.
            // This allows us to continue without awaiting the runActionTask upon timeout.
            var completedTask = await Task.WhenAny(runActionTask, actionTimeoutTask).ConfigureAwait(false);

            if (completedTask == actionTimeoutTask)
            {
                // Throw on cancellation requested if that's the reason the timeout task completed.
                token.ThrowIfCancellationRequested();

                // If the timeout is hit and the ContinuationOnTimeout flag is set, commit a new ActionResponse 
                // with the status set to TimeoutOnAction and return.
                if (treeAction.ContinuationOnTimeout)
                {
                    // Trigger linked cancellation token before continuing to ensure the runActionTask gets cancelled.
                    actionCts.Cancel();

                    ActionResponse timeoutResponse = new ActionResponse
                    {
                        Status = "TimeoutOnAction"
                    };

                    await this.CommitActionResponse(treeActionKey, timeoutResponse).ConfigureAwait(false);
                    return;
                }

                // ActionTimeout has been hit. Throw special exception to indicate this.
                throw new ActionTimeoutException(string.Format(
                    "ActionTimeoutTask timed out before Action could complete. TreeNodeKey: {0}, TreeActionKey: {1}, ActionName: {2}.",
                    treeNodeKey,
                    treeActionKey,
                    treeAction.Action));
            }
            else
            {
                // Handle the completed runActionTask.
                if (runActionTask.Status == TaskStatus.RanToCompletion)
                {
                    await this.CommitActionResponse(treeActionKey, await runActionTask).ConfigureAwait(false);
                }

                // Await the completed task to propagate any exceptions.
                // Exceptions are thrown here if the action hit a timeout, was cancelled, or failed.
                await runActionTask;
            }
        }