diff --git a/src/Build/Evaluation/ConditionEvaluator.cs b/src/Build/Evaluation/ConditionEvaluator.cs index cbe29768a60..d10daf38909 100644 --- a/src/Build/Evaluation/ConditionEvaluator.cs +++ b/src/Build/Evaluation/ConditionEvaluator.cs @@ -139,20 +139,20 @@ static bool ContainsInvalidCharacter(ReadOnlySpan span) private struct ExpressionTreeForCurrentOptionsWithSize { // condition string -> pool of expression trees - private readonly ConcurrentDictionary> _conditionPools; + private readonly ConcurrentDictionary> _conditionPools; private int _mOptimisticSize; public readonly int OptimisticSize => _mOptimisticSize; - public ExpressionTreeForCurrentOptionsWithSize(ConcurrentDictionary> conditionPools) + public ExpressionTreeForCurrentOptionsWithSize(ConcurrentDictionary> conditionPools) { _conditionPools = conditionPools; _mOptimisticSize = conditionPools.Count; } - public ConcurrentStack GetOrAdd(string condition, Func> addFunc) + public Stack GetOrAdd(string condition, Func> addFunc) { - if (!_conditionPools.TryGetValue(condition, out var stack)) + if (!_conditionPools.TryGetValue(condition, out Stack? stack)) { // Count how many conditions there are in the cache. // The condition evaluator will flush the cache when some threshold is exceeded. @@ -241,61 +241,69 @@ internal static bool EvaluateConditionCollectingConditionedProperties( // Get the expression tree cache for the current parsing options. var cachedExpressionTreesForCurrentOptions = s_cachedExpressionTrees.GetOrAdd( (int)options, - _ => new ExpressionTreeForCurrentOptionsWithSize(new ConcurrentDictionary>(StringComparer.Ordinal))); + _ => new ExpressionTreeForCurrentOptionsWithSize(new ConcurrentDictionary>(StringComparer.Ordinal))); cachedExpressionTreesForCurrentOptions = FlushCacheIfLargerThanThreshold(options, cachedExpressionTreesForCurrentOptions); // Get the pool of expressions for this condition. - var expressionPool = cachedExpressionTreesForCurrentOptions.GetOrAdd(condition, _ => new ConcurrentStack()); + Stack expressionPool = cachedExpressionTreesForCurrentOptions.GetOrAdd(condition, _ => new Stack()); - // 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. - if (!expressionPool.TryPop(out var parsedExpression)) + lock (expressionPool) { - var conditionParser = new Parser(); - - #region REMOVE_COMPAT_WARNING - conditionParser.LoggingServices = loggingContext?.LoggingService; - conditionParser.LogBuildEventContext = loggingContext?.BuildEventContext ?? BuildEventContext.Invalid; - #endregion - - parsedExpression = conditionParser.Parse(condition, options, elementLocation); - } + // 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.Count == 0) + { + var conditionParser = new Parser(); - bool result; + #region REMOVE_COMPAT_WARNING + conditionParser.LoggingServices = loggingContext?.LoggingService; + conditionParser.LogBuildEventContext = loggingContext?.BuildEventContext ?? BuildEventContext.Invalid; + #endregion - var state = new ConditionEvaluationState( - condition, - expander, - expanderOptions, - conditionedPropertiesTable, - evaluationDirectory, - elementLocation, - fileSystem, - projectRootElementCache); - - expander.PropertiesUseTracker.PropertyReadContext = PropertyReadContext.ConditionEvaluation; - // 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 + parsedExpression = conditionParser.Parse(condition, options, elementLocation); + } + else { - result = parsedExpression.Evaluate(state); + parsedExpression = expressionPool.Pop(); } - finally + + bool result; + + var state = new ConditionEvaluationState( + condition, + expander, + expanderOptions, + conditionedPropertiesTable, + evaluationDirectory, + elementLocation, + fileSystem, + projectRootElementCache); + + expander.PropertiesUseTracker.PropertyReadContext = PropertyReadContext.ConditionEvaluation; + // 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) { - parsedExpression.ResetState(); - if (!s_disableExpressionCaching) + try + { + result = parsedExpression.Evaluate(state); + } + finally { - // Finished using the expression tree. Add it back to the pool so other threads can use it. - expressionPool.Push(parsedExpression); + 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); + } + expander.PropertiesUseTracker.ResetPropertyReadContext(); } - expander.PropertiesUseTracker.ResetPropertyReadContext(); } - } - return result; + return result; + } } private static ExpressionTreeForCurrentOptionsWithSize FlushCacheIfLargerThanThreshold( @@ -317,14 +325,14 @@ private static ExpressionTreeForCurrentOptionsWithSize FlushCacheIfLargerThanThr (int)options, _ => new ExpressionTreeForCurrentOptionsWithSize( - new ConcurrentDictionary>(StringComparer.Ordinal)), + new ConcurrentDictionary>(StringComparer.Ordinal)), (key, existing) => { if (existing.OptimisticSize > 3000) { return new ExpressionTreeForCurrentOptionsWithSize( - new ConcurrentDictionary>(StringComparer.Ordinal)); + new ConcurrentDictionary>(StringComparer.Ordinal)); } else {