Uploaded image for project: 'Drools'
  1. Drools
  2. DROOLS-7648

[Performance Issue] Let's change to don't use the Forkjoin common pool

XMLWordPrintable

    • Icon: Enhancement Enhancement
    • Resolution: Unresolved
    • Icon: Major Major
    • None
    • 7.74.1.Final
    • None
    • None
    • NEW
    • NEW
    • ---
    • ---

       

      Hi team of Drools,

       

      Background:

      When Drools compiles rules at runtime—especially when dealing with hundreds of rules—it uses the following construct to parallelize rule compilation:

       

      rules.stream().parallel() 

      (a part of KnowledgeBuilderImpl.java, The full source code is provided below.)

      This implicitly delegates to the ForkJoin common pool, which is shared across the entire JVM.
      As a result, if other parts of the application also rely on the common pool for parallel or asynchronous processing, contention can occur. 

      In my case, this shared usage negatively impacted performance in unrelated components due to thread starvation in the common pool.
      The issue was particularly evident during loadClass(xxx):

      Each rule references the same Java class in the then clause. This led to contention and thread blocks inside the common pool while repeatedly calling loadClass.

       

      Interestingly, I noticed this snippet in your codebase:

      public static class ForkJoinPoolHolder {
          public static final ForkJoinPool COMPILER_POOL = new ForkJoinPool(); // avoid common pool
      } 

      This seems to acknowledge the potential risk of using the common pool—but it’s not consistently applied across the rule compilation flow.

       

      Suggestion

      To mitigate side effects and improve system isolation, I suggest explicitly using a dedicated ForkJoinPool (such as ForkJoinPoolHolder.COMPILER_POOL) during rule compilation, rather than relying on parallelStream() which uses the common pool by default.

       

      By avoiding the use of the common pool and instead utilizing a dedicated ForkJoinPool, we can isolate the thread pool used for rule compilation, ensuring it does not interfere with other parts of the application. This separation is especially critical in environments with shared JVM workloads or intensive parallel operations.

       

      If you agree with this suggestion, I would appreciate the opportunity to contribute the code changes to improve this 

       

      Thanks

      Chanwoo.

       

      KnowledgeBuilderImpl 

       

      public static class ForkJoinPoolHolder {
          public static final ForkJoinPool COMPILER_POOL = new ForkJoinPool(); // avoid common pool
      }
      
      private void compileRulesLevel(PackageDescr packageDescr, PackageRegistry pkgRegistry, List<RuleDescr> rules) {
          boolean parallelRulesBuild = this.kBase == null && parallelRulesBuildThreshold != -1 && rules.size() > parallelRulesBuildThreshold;
          if (parallelRulesBuild) {
              Map<String, RuleBuildContext> ruleCxts = new ConcurrentHashMap<>();
              try {
                  ForkJoinPoolHolder.COMPILER_POOL.submit(() ->
                                                                  rules.stream().parallel()
                                                                          .filter(ruleDescr -> filterAccepts(ResourceChange.Type.RULE, ruleDescr.getNamespace(), ruleDescr.getName()))
                                                                          .forEach(ruleDescr -> {
                                                                              initRuleDescr(packageDescr, pkgRegistry, ruleDescr);
                                                                              RuleBuildContext context = buildRuleBuilderContext(pkgRegistry, ruleDescr);
                                                                              ruleCxts.put(ruleDescr.getName(), context);
                                                                              List<? extends KnowledgeBuilderResult> results = addRule(context);
                                                                              if (!results.isEmpty()) {
                                                                                  synchronized (this.results) {
                                                                                      this.results.addAll(results);
                                                                                  }
                                                                              }
                                                                          })
                  ).get();
              } catch (InterruptedException | ExecutionException e) {
                  throw new RuntimeException("Rules compilation failed or interrupted", e);
              }
              for (RuleDescr ruleDescr : rules) {
                  RuleBuildContext context = ruleCxts.get(ruleDescr.getName());
                  if (context != null) {
                      pkgRegistry.getPackage().addRule(context.getRule());
                  }
              }
          } else {
              for (RuleDescr ruleDescr : rules) {
                  if (filterAccepts(ResourceChange.Type.RULE, ruleDescr.getNamespace(), ruleDescr.getName())) {
                      initRuleDescr(packageDescr, pkgRegistry, ruleDescr);
                      RuleBuildContext context = buildRuleBuilderContext(pkgRegistry, ruleDescr);
                      this.results.addAll(addRule(context));
                      pkgRegistry.getPackage().addRule(context.getRule());
                  }
              }
          }
      } 

       

       

              mfusco@redhat.com Mario Fusco
              pove2019 CHANWOO BAE (Inactive)
              Votes:
              0 Vote for this issue
              Watchers:
              1 Start watching this issue

                Created:
                Updated: