private static ExpressionTreeForCurrentOptionsWithSize FlushCacheIfLargerThanThreshold( ParserOptions options, ExpressionTreeForCurrentOptionsWithSize cachedExpressionTreesForCurrentOptions) { if (cachedExpressionTreesForCurrentOptions.OptimisticSize > 3000) { // 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. cachedExpressionTreesForCurrentOptions = s_cachedExpressionTrees.AddOrUpdate( (int)options, _ => new ExpressionTreeForCurrentOptionsWithSize( new ConcurrentDictionary <string, ConcurrentStack <GenericExpressionNode> >(StringComparer.Ordinal)), (key, existing) => { if (existing.OptimisticSize > 3000) { return (new ExpressionTreeForCurrentOptionsWithSize( new ConcurrentDictionary <string, ConcurrentStack <GenericExpressionNode> >(StringComparer.Ordinal))); } else { return(existing); } }); } return(cachedExpressionTreesForCurrentOptions); }
/// <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"); // Get the expression tree cache for the current parsing options. ExpressionTreeForCurrentOptionsWithSize cachedExpressionTreesForCurrentOptions = s_cachedExpressionTrees.GetOrAdd( (int)options, _ => new ExpressionTreeForCurrentOptionsWithSize(new ConcurrentDictionary <string, ConcurrentStack <GenericExpressionNode> >(StringComparer.Ordinal))); cachedExpressionTreesForCurrentOptions = FlushCacheIfLargerThanThreshold(options, cachedExpressionTreesForCurrentOptions); // Get the pool of expressions for this condition. var expressionPool = cachedExpressionTreesForCurrentOptions.GetOrAdd(condition, _ => new ConcurrentStack <GenericExpressionNode>()); // Try and see if there's an available expression tree in the pool. // If not, parse a new expression tree and add it back to the pool. GenericExpressionNode parsedExpression; if (!expressionPool.TryPop(out parsedExpression)) { Parser conditionParser = new Parser(); #region REMOVE_COMPAT_WARNING conditionParser.LoggingServices = loggingServices; conditionParser.LogBuildEventContext = buildEventContext; #endregion parsedExpression = conditionParser.Parse(condition, options, elementLocation); } 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(); if (!s_disableExpressionCaching) { // Finished using the expression tree. Add it back to the pool so other threads can use it. expressionPool.Push(parsedExpression); } } } return(result); }