/// <summary> /// If any expression nodes cache any state for the duration of evaluation, /// now's the time to clean it up /// </summary> internal override void ResetState() { if (_leftChild != null) { _leftChild.ResetState(); } if (_rightChild != null) { _rightChild.ResetState(); } }
/// <summary> /// Evaluates a string representing a condition from a "condition" attribute. /// If the condition is a malformed string, it throws an InvalidProjectFileException. /// This method uses cached expression trees to avoid generating them from scratch every time it's called. /// This method is thread safe and is called from engine and task execution module threads /// Logging service may be null. /// </summary> internal static bool EvaluateConditionCollectingConditionedProperties <P, I> ( string condition, ParserOptions options, Expander <P, I> expander, ExpanderOptions expanderOptions, Dictionary <string, List <string> > conditionedPropertiesTable, string evaluationDirectory, ElementLocation elementLocation, ILoggingService loggingServices, BuildEventContext buildEventContext, ProjectRootElementCache projectRootElementCache = null ) where P : class, IProperty where I : class, IItem { ErrorUtilities.VerifyThrowArgumentNull(condition, "condition"); ErrorUtilities.VerifyThrowArgumentNull(expander, "expander"); ErrorUtilities.VerifyThrowArgumentLength(evaluationDirectory, "evaluationDirectory"); ErrorUtilities.VerifyThrowArgumentNull(buildEventContext, "buildEventContext"); // An empty condition is equivalent to a "true" condition. if (condition.Length == 0) { return(true); } // If the condition wasn't empty, there must be a location for it ErrorUtilities.VerifyThrowArgumentNull(elementLocation, "elementLocation"); Hashtable cachedExpressionTreesForCurrentOptions = s_cachedExpressionTrees[(int)options]; // We only need to lock on writes to the table if (cachedExpressionTreesForCurrentOptions == null) { // Given property functions, casing in conditional expressions isn't necessarily ignored. cachedExpressionTreesForCurrentOptions = new Hashtable(50, StringComparer.Ordinal); lock (s_cachedExpressionTrees) { s_cachedExpressionTrees[(int)options] = cachedExpressionTreesForCurrentOptions; } } // VS stress tests could fill up this cache without end, for example if they use // random configuration names - those appear in conditional expressions. // So if we hit a limit that we should never hit in normal circumstances in VS, // and rarely, periodically hit in normal circumstances in large tree builds, // just clear out the cache. It can start repopulating again. Some kind of prioritized // aging isn't worth it: although the hit rate of these caches is excellent (nearly 100%) // the cost of reparsing expressions should the cache be cleared is not particularly large. // Loading Australian Government in VS, there are 3 of these tables, two with about 50 // entries and one with about 650 entries. So 3000 seems large enough. if (cachedExpressionTreesForCurrentOptions.Count > 3000) // threadsafe { lock (cachedExpressionTreesForCurrentOptions) { cachedExpressionTreesForCurrentOptions.Clear(); } } // Try and see if we have an expression tree for this condition already GenericExpressionNode parsedExpression = (GenericExpressionNode)cachedExpressionTreesForCurrentOptions[condition]; if (parsedExpression == null) { Parser conditionParser = new Parser(); #region REMOVE_COMPAT_WARNING conditionParser.LoggingServices = loggingServices; conditionParser.LogBuildEventContext = buildEventContext; #endregion parsedExpression = conditionParser.Parse(condition, options, elementLocation); // It's possible two threads will add a different tree to the same entry in the hashtable, // but it should be rare and it's not a problem - the previous entry will be thrown away. // We could ensure no dupes with double check locking but it's not really necessary here. // Also, we don't want to lock on every read. lock (cachedExpressionTreesForCurrentOptions) { if (!s_disableExpressionCaching) { cachedExpressionTreesForCurrentOptions[condition] = parsedExpression; } } } bool result; ConditionEvaluationState <P, I> state = new ConditionEvaluationState <P, I>(condition, expander, expanderOptions, conditionedPropertiesTable, evaluationDirectory, elementLocation, projectRootElementCache); // We are evaluating this expression now and it can cache some state for the duration, // so we don't want multiple threads working on the same expression lock (parsedExpression) { try { result = parsedExpression.Evaluate(state); } finally { parsedExpression.ResetState(); } } return(result); }