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