diff --git a/src/RulesEngine/Models/ReSettings.cs b/src/RulesEngine/Models/ReSettings.cs index a4a4262c..ef392277 100644 --- a/src/RulesEngine/Models/ReSettings.cs +++ b/src/RulesEngine/Models/ReSettings.cs @@ -30,6 +30,7 @@ internal ReSettings(ReSettings reSettings) UseFastExpressionCompiler = reSettings.UseFastExpressionCompiler; EnableExceptionAsErrorMessageForRuleExpressionParsing = reSettings.EnableExceptionAsErrorMessageForRuleExpressionParsing; AutoExecuteActions = reSettings.AutoExecuteActions; + EnableParallelRuleCompilation = reSettings.EnableParallelRuleCompilation; } @@ -98,6 +99,13 @@ internal ReSettings(ReSettings reSettings) /// run actions yourself (e.g. via ExecuteActionWorkflowAsync) for selective control. See #596. /// public bool AutoExecuteActions { get; set; } = true; + + /// + /// When true, rules within a workflow are compiled in parallel during registration. + /// Significantly reduces warmup time for workflows with many rules. + /// Default: false + /// + public bool EnableParallelRuleCompilation { get; set; } = false; } public enum NestedRuleExecutionMode diff --git a/src/RulesEngine/RulesEngine.cs b/src/RulesEngine/RulesEngine.cs index a7d715ff..3e03d5a0 100644 --- a/src/RulesEngine/RulesEngine.cs +++ b/src/RulesEngine/RulesEngine.cs @@ -399,9 +399,33 @@ private bool RegisterRule(string workflowName, params RuleParameter[] ruleParams _rulesCache.AddOrUpdateGlobalParamsDelegate(compileRulesKey, globalParamsDelegate); } - foreach (var rule in workflow.Rules.Where(c => c.Enabled)) + var enabledRules = workflow.Rules.Where(c => c.Enabled).ToArray(); + var compiledFuncs = new RuleFunc[enabledRules.Length]; + if (_reSettings.EnableParallelRuleCompilation) { - dictFunc.Add(rule.RuleName, CompileRule(rule,workflow.RuleExpressionType, ruleParams, globalParamExp)); + try + { + System.Threading.Tasks.Parallel.For(0, enabledRules.Length, i => { + compiledFuncs[i] = CompileRule(enabledRules[i], workflow.RuleExpressionType, ruleParams, globalParamExp); + }); + } + catch (AggregateException ae) + { + // Preserve the serial-compilation contract: the first rule that fails + // to compile surfaces its own exception, not an AggregateException. + System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(ae.InnerExceptions[0]).Throw(); + } + } + else + { + for (var i = 0; i < enabledRules.Length; i++) + { + compiledFuncs[i] = CompileRule(enabledRules[i], workflow.RuleExpressionType, ruleParams, globalParamExp); + } + } + for (var i = 0; i < enabledRules.Length; i++) + { + dictFunc.Add(enabledRules[i].RuleName, compiledFuncs[i]); } _rulesCache.AddOrUpdateCompiledRule(compileRulesKey, dictFunc);