Skip to content

Commit 09762c6

Browse files
benluersenclaude
andauthored
Fix warmup regression: avoid per-expression AppDomain assembly scan (#740)
Since 71d59dc, CustomTypeProvider.GetCustomTypes() includes base.GetCustomTypes(), which scans every assembly in the AppDomain for [DynamicLinqType] types. DefaultDynamicLinqCustomTypeProvider only caches that scan per provider instance, and RuleExpressionParser.Parse built a new ParsingConfig + CustomTypeProvider for every expression parsed, so the scan ran for every expression in every rule (KeywordsHelper enumerates the full type set on each ExpressionParser construction). For workflows with thousands of rules this regressed warmup ~6.6x versus 5.0.3 (113.8s vs 17.3s for 20,000 rules with local params and 174 loaded assemblies). The compiled-delegate cache from #727 does not help here because each rule expression is unique. Fix: - Reuse one ParsingConfig/CustomTypeProvider across parses, rebuilding only when ReSettings.CustomTypes is swapped (AutoRegisterInputType replaces the array on workflow registration). - Memoize the merged custom-type set in CustomTypeProvider; it is fixed after construction but was rebuilt (including the assembly scan) on every GetCustomTypes() call. With this change the same 20,000-rule benchmark warms up in 16.3s, matching 5.0.3, with identical rule results. Addresses the remaining root cause behind #707. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 354f765 commit 09762c6

2 files changed

Lines changed: 36 additions & 7 deletions

File tree

src/RulesEngine/CustomTypeProvider.cs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,19 @@ public CustomTypeProvider(Type[] types) : base(ParsingConfig.Default)
4242
_types.Add(typeof(IEnumerable));
4343
}
4444

45+
private HashSet<Type> _mergedTypes;
46+
4547
public override HashSet<Type> GetCustomTypes()
4648
{
47-
var all = new HashSet<Type>(base.GetCustomTypes());
48-
all.UnionWith(_types);
49-
return all;
49+
// base.GetCustomTypes() scans every assembly in the AppDomain for [DynamicLinqType].
50+
// The provider's type set is fixed after construction, so merge exactly once.
51+
if (_mergedTypes == null)
52+
{
53+
var all = new HashSet<Type>(base.GetCustomTypes());
54+
all.UnionWith(_types);
55+
_mergedTypes = all;
56+
}
57+
return _mergedTypes;
5058
}
5159
}
5260
}

src/RulesEngine/ExpressionBuilders/RuleExpressionParser.cs

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,33 @@ private void PopulateMethodInfo()
8383
_methodInfo.Add("dict_add", dict_add);
8484
}
8585

86+
private ParsingConfig _cachedParsingConfig;
87+
private Type[] _cachedParsingConfigCustomTypes;
88+
89+
private ParsingConfig GetParsingConfig()
90+
{
91+
// Building a CustomTypeProvider is expensive: System.Linq.Dynamic.Core's
92+
// DefaultDynamicLinqCustomTypeProvider scans all AppDomain assemblies for
93+
// [DynamicLinqType] and only caches per provider instance. Reuse one config
94+
// until ReSettings.CustomTypes is swapped (AutoRegisterInputType does this
95+
// on workflow registration).
96+
var customTypes = _reSettings.CustomTypes;
97+
var config = _cachedParsingConfig;
98+
if (config == null || !ReferenceEquals(_cachedParsingConfigCustomTypes, customTypes))
99+
{
100+
config = new ParsingConfig {
101+
CustomTypeProvider = new CustomTypeProvider(customTypes),
102+
IsCaseSensitive = _reSettings.IsExpressionCaseSensitive
103+
};
104+
_cachedParsingConfigCustomTypes = customTypes;
105+
_cachedParsingConfig = config;
106+
}
107+
return config;
108+
}
109+
86110
public Expression Parse(string expression, ParameterExpression[] parameters, Type returnType)
87111
{
88-
var config = new ParsingConfig {
89-
CustomTypeProvider = new CustomTypeProvider(_reSettings.CustomTypes),
90-
IsCaseSensitive = _reSettings.IsExpressionCaseSensitive
91-
};
112+
var config = GetParsingConfig();
92113

93114
// Instead of immediately returning default values, allow for expression parsing to handle dynamic evaluation.
94115
try

0 commit comments

Comments
 (0)