diff --git a/Forge.TreeWalker/src/ActionContext.cs b/Forge.TreeWalker/src/ActionContext.cs index 8babc5c..4cf3fa5 100644 --- a/Forge.TreeWalker/src/ActionContext.cs +++ b/Forge.TreeWalker/src/ActionContext.cs @@ -100,14 +100,13 @@ public ActionContext( string treeName, Guid rootSessionId) { - if (sessionId == null) throw new ArgumentNullException("sessionId"); + if (sessionId == Guid.Empty) throw new ArgumentException("sessionId cannot be empty.", "sessionId"); if (string.IsNullOrWhiteSpace(treeNodeKey)) throw new ArgumentNullException("treeNodeKey"); if (string.IsNullOrWhiteSpace(actionName)) throw new ArgumentNullException("actionName"); if (userContext == null) throw new ArgumentNullException("userContext"); - if (token == null) throw new ArgumentNullException("token"); if (forgeState == null) throw new ArgumentNullException("forgeState"); if (string.IsNullOrWhiteSpace(treeName)) throw new ArgumentNullException("treeName"); - if (rootSessionId == null) throw new ArgumentNullException("rootSessionId"); + if (rootSessionId == Guid.Empty) throw new ArgumentException("rootSessionId cannot be empty.", "rootSessionId"); this.SessionId = sessionId; this.TreeNodeKey = treeNodeKey; diff --git a/Forge.TreeWalker/src/ActionDefinition.cs b/Forge.TreeWalker/src/ActionDefinition.cs index f08280e..25cbe01 100644 --- a/Forge.TreeWalker/src/ActionDefinition.cs +++ b/Forge.TreeWalker/src/ActionDefinition.cs @@ -10,7 +10,6 @@ namespace Microsoft.Forge.TreeWalker { using System; - using System.Threading.Tasks; /// /// The ActionDefinition class holds definitions for the action. diff --git a/Forge.TreeWalker/src/ExpressionExecutor.cs b/Forge.TreeWalker/src/ExpressionExecutor.cs index 51bf3bd..4fc6a23 100644 --- a/Forge.TreeWalker/src/ExpressionExecutor.cs +++ b/Forge.TreeWalker/src/ExpressionExecutor.cs @@ -243,12 +243,12 @@ private class MissingResolver : Microsoft.CodeAnalysis.MetadataReferenceResolver { public override bool Equals(object other) { - throw new NotImplementedException(); + return other is MissingResolver; } public override int GetHashCode() { - throw new NotImplementedException(); + return typeof(MissingResolver).GetHashCode(); } public override bool ResolveMissingAssemblies => false; diff --git a/Forge.TreeWalker/src/ForgeSchemaValidator.cs b/Forge.TreeWalker/src/ForgeSchemaValidator.cs index ea2792c..7f044fc 100644 --- a/Forge.TreeWalker/src/ForgeSchemaValidator.cs +++ b/Forge.TreeWalker/src/ForgeSchemaValidator.cs @@ -1,4 +1,4 @@ -//----------------------------------------------------------------------- +//----------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. All rights reserved. // @@ -165,15 +165,13 @@ private static List ConvertStringToJObjectList(string schema, bool vali private static JObject SerializeToJObject(object forgeTree) { - string stringSchema = JsonConvert.SerializeObject( - forgeTree, - new JsonSerializerSettings + JsonSerializer serializer = JsonSerializer.Create(new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Ignore, // Prevent default values from getting added to serialized json schema. Converters = new List { new Newtonsoft.Json.Converters.StringEnumConverter() } // Use string enum values instead of numerical. }); - return JObject.Parse(stringSchema); + return JObject.FromObject(forgeTree, serializer); } private static List GetSchemaFromPath(string path, bool validateAsDictionary) diff --git a/Forge.TreeWalker/src/TreeNodeContext.cs b/Forge.TreeWalker/src/TreeNodeContext.cs index 82b13f0..ba6a770 100644 --- a/Forge.TreeWalker/src/TreeNodeContext.cs +++ b/Forge.TreeWalker/src/TreeNodeContext.cs @@ -83,11 +83,10 @@ public TreeNodeContext( Guid rootSessionId, string currentNodeSkipActionContext) { - if (sessionId == null) throw new ArgumentNullException("sessionId"); + if (sessionId == Guid.Empty) throw new ArgumentException("sessionId cannot be empty.", "sessionId"); if (string.IsNullOrWhiteSpace(treeNodeKey)) throw new ArgumentNullException("treeNodeKey"); - if (token == null) throw new ArgumentNullException("token"); if (string.IsNullOrWhiteSpace(treeName)) throw new ArgumentNullException("treeName"); - if (rootSessionId == null) throw new ArgumentNullException("rootSessionId"); + if (rootSessionId == Guid.Empty) throw new ArgumentException("rootSessionId cannot be empty.", "rootSessionId"); this.SessionId = sessionId; this.TreeNodeKey = treeNodeKey; diff --git a/Forge.TreeWalker/src/TreeWalkerParameters.cs b/Forge.TreeWalker/src/TreeWalkerParameters.cs index 2092946..f7c8160 100644 --- a/Forge.TreeWalker/src/TreeWalkerParameters.cs +++ b/Forge.TreeWalker/src/TreeWalkerParameters.cs @@ -162,7 +162,6 @@ public TreeWalkerParameters( if (forgeTree == null) throw new ArgumentNullException("forgeTree"); if (forgeState == null) throw new ArgumentNullException("forgeState"); if (callbacks == null) throw new ArgumentNullException("callbacks"); - if (token == null) throw new ArgumentNullException("token"); this.SessionId = sessionId; this.ForgeTree = forgeTree; @@ -192,7 +191,6 @@ public TreeWalkerParameters( if (string.IsNullOrWhiteSpace(jsonSchema)) throw new ArgumentNullException("jsonSchema"); if (forgeState == null) throw new ArgumentNullException("forgeState"); if (callbacks == null) throw new ArgumentNullException("callbacks"); - if (token == null) throw new ArgumentNullException("token"); this.SessionId = sessionId; this.JsonSchema = jsonSchema; @@ -230,7 +228,6 @@ public TreeWalkerParameters( if (forgeTree == null) throw new ArgumentNullException("forgeTree"); if (forgeState == null) throw new ArgumentNullException("forgeState"); if (callbacksV2 == null) throw new ArgumentNullException("callbacksV2"); - if (token == null) throw new ArgumentNullException("token"); this.SessionId = sessionId; this.ForgeTree = forgeTree; diff --git a/Forge.TreeWalker/src/TreeWalkerSession.cs b/Forge.TreeWalker/src/TreeWalkerSession.cs index 38efbf3..7ba460d 100644 --- a/Forge.TreeWalker/src/TreeWalkerSession.cs +++ b/Forge.TreeWalker/src/TreeWalkerSession.cs @@ -80,6 +80,11 @@ public class TreeWalkerSession : ITreeSession /// public static string DefaultTreeName = "RootTree"; + /// + /// Cached MethodInfo for BaseAction.RunAction to avoid per-execution reflection overhead. + /// + private static readonly MethodInfo RunActionMethod = typeof(BaseAction).GetMethod("RunAction"); + /// /// The Roslyn regex expression. Used to check if dynamic schema values should be evaluated with Roslyn. /// Type can be added to indicate that Roslyn should evaluate the expression and return the specified type. @@ -156,7 +161,9 @@ public TreeWalkerSession(TreeWalkerParameters parameters) } // Initialize properties from required TreeWalkerParameters properties. - this.Schema = parameters.ForgeTree ?? JsonConvert.DeserializeObject(parameters.JsonSchema); + this.Schema = parameters.ForgeTree ?? JsonConvert.DeserializeObject( + parameters.JsonSchema, + new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.None }); this.walkTreeCts = CancellationTokenSource.CreateLinkedTokenSource(parameters.Token); // Initialize properties from optional TreeWalkerParameters properties. @@ -676,7 +683,7 @@ internal async Task PerformActionTypeBehavior(TreeNode treeNode, string treeNode } } - // Wait for all parallel tasks to complete until the given timout. + // Wait for all parallel tasks to complete until the given timeout. // If any task hits a timeout, gets cancelled, or fails, an exception will be thrown. // Note: CancelWalkTree is called at the end of every session to ensure all Actions/Tasks see the triggered cancellation token. Task nodeTimeoutTask = Task.Delay((int)await this.EvaluateDynamicProperty(treeNode.Timeout ?? -1, typeof(int)).ConfigureAwait(false), this.walkTreeCts.Token); @@ -740,7 +747,7 @@ internal async Task ExecuteActionWithRetry( Task actionTimeoutTask = Task.Delay(actionTimeout, token); stopwatch.Start(); - // Attmpt to ExecuteAction based on RetryPolicy and Timeout. + // Attempt to ExecuteAction based on RetryPolicy and Timeout. // Throw on non-retriable exceptions. while ( (retryPolicyType != RetryPolicyType.FixedCount || (retryPolicyType == RetryPolicyType.FixedCount && maxRetryCount > 0)) && (actionTimeout == -1 || stopwatch.ElapsedMilliseconds < actionTimeout)) @@ -892,7 +899,9 @@ internal async Task ExecuteAction( { // 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); + using (CancellationTokenSource actionCts = CancellationTokenSource.CreateLinkedTokenSource(token)) + { + token = treeAction.ContinuationOnTimeout ? actionCts.Token : token; // Evaluate the dynamic properties that are used by the actionTask. @@ -922,8 +931,7 @@ await this.EvaluateDynamicProperty(treeAction.Properties, null).ConfigureAwait(f actionObject = Activator.CreateInstance(actionDefinition.ActionType); } - MethodInfo method = typeof(BaseAction).GetMethod("RunAction"); - Task runActionTask = (Task) method.Invoke(actionObject, new object[] { actionContext }); + Task runActionTask = (Task) RunActionMethod.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. @@ -969,6 +977,7 @@ await this.EvaluateDynamicProperty(treeAction.Properties, null).ConfigureAwait(f // Exceptions are thrown here if the action hit a timeout, was cancelled, or failed. await runActionTask; } + } // end using actionCts } ///