/// <summary> /// This function is a little complex: /// /// 1. It needs to be thread-safe and atomic. This means the iteration over the list /// needs to happen w/o releasing a lock, and the tests also get run under lock /// (they’re simple operations and therefore are safe to do this). But the target /// is not safe to run under a lock because it may block and cause a deadlock. Therefore /// we need to release the lock before running the target. /// 2. The code for doing the evaluation is a little complex, and the threading issues makes /// it more complex. Therefore factoring it into a version which just looks for a rule, /// and a version which looks and executes a rule is either going to obscure the locking issues /// or result in a reasonable duplication of code which may be error prone to maintain. /// 3. The updates of the sites needs to happen before the execution of the rule. This /// is due to both stack overflow and performance issues. If a function goes recursive and we /// haven’t updated the recursive-call site before then we’ll repeatedly generate and evaluate /// rules until we stack overflow, and if we don’t stack overflow our perf will /// suck until the method unwinds once. /// /// For those reasons we get the one big method which takes 7 parameters to handle both updating /// and executing. One of those parameters is the bool flag to indicate that we should execute /// the rule (that’s how that decision is made). 3 more are the result of the execution and the /// target/rule list to update for the caller. One more is the site object which we need to lock /// on to make the update to the site atomic. And finally we get the CodeContext which now flows /// in instead of having RuleTree hold onto a LanguageContext. This is because we need /// the full CodeContext to execute the test/target and it needs to be the real CodeContext so /// we get the proper set of locals/globals flowing through. /// </summary> private StandardRule <T> GetRuleMaybeExecute(CodeContext callerContext, object[] args, bool execute, object site, ref T target, ref RuleSet <T> rules, out object result) { // TODO: We can do better granularity than just types. LinkedList <StandardRule <T> > ruleList = GetRuleList(args); if (DynamicSiteHelpers.IsBigTarget(typeof(T))) { args = new object[] { Tuple.MakeTuple(typeof(T).GetGenericArguments()[0], args) }; } bool lockReleased = false; int index = 0; Monitor.Enter(ruleList); try { LinkedListNode <StandardRule <T> > node = ruleList.First; while (node != null) { StandardRule <T> rule = node.Value; if (!rule.IsValid) { LinkedListNode <StandardRule <T> > nodeToRemove = node; node = node.Next; ruleList.Remove(nodeToRemove); continue; } PerfTrack.NoteEvent(PerfTrack.Categories.RuleEvaluation, "Evaluating " + index++ + " rule in tree"); CodeContext tmpCtx = callerContext.Scope.GetTemporaryVariableContext(callerContext, rule.ParamVariables, args); try { if ((bool)rule.Test.Evaluate(tmpCtx)) { // Tentative optimization of moving rule to front of list when found ruleList.Remove(node); ruleList.AddFirst(node); if (site != null) { DynamicSiteHelpers.UpdateSite <T>(callerContext, site, ref target, ref rules, rule); } // release the lock for calling the target which may block, we assume // the test performs no synchronization Monitor.Exit(ruleList); lockReleased = true; if (execute) { result = rule.Target.Execute(tmpCtx); } else { result = null; } return(rule); } } finally { tmpCtx.Scope.TemporaryStorage.Clear(); } node = node.Next; } } finally { if (!lockReleased) { Monitor.Exit(ruleList); } } PerfTrack.NoteEvent(PerfTrack.Categories.Rules, "NoMatch" + index); result = null; return(null); }
public StandardRule <T> ExecuteRuleAndUpdateSite(CodeContext callerContext, object[] args, object site, ref T target, ref RuleSet <T> rules, out object result) { return(GetRuleMaybeExecute(callerContext, args, true, site, ref target, ref rules, out result)); }