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

Rule is only fired after reloading the container

    XMLWordPrintable

Details

    • Bug
    • Resolution: Not a Bug
    • Major
    • None
    • 7.73.0.Final
    • core engine
    • None
    • 2023 Week 03-05 (from Jan 16), 2023 Week 06-08 (from Feb 6)
    • Hide
      number Step Clarification
      1 Drools KieContainer fully operational it needs to have at least one session that has been fired all rules
      2 Create a rule and add it to the container the rule will use an interface that has never been used before in any rule
      3 Create a new KieSession in that container  
      4 Fire all rules in that container  
      5 Check if the rule has been triggered the rule does not trigger at all
      6 Reload the whole container  
      7 Fire the rules again the process of firing the rules is exactly the same as in the previous steps
      8 Check if the rule has been triggered the rule is triggered and works exactly as expected  

      How the rule is added to the container

      Resource resource = newRule.toResource();
      kieFileSystem.write(resource);
      IncrementalResults results = ((InternalKieBuilder) kieBuilder).incrementalBuild();
      ... process results which does not affect the container ...
      kieContainer.updateToVersion(releaseId);

      Summary of the rule itself

      package ...
      some imports...
      rule "rule-name"
      when
          $something : SomeInteface(property1 == "some value", property2 == "some other value");
      then
          some logic to write a message    
      end
      

      What I mean when I say "reload the container"

      KieServices kieServices = KieServices.Factory.get();
      ReleaseId releaseId = kieServices.newReleaseId(--GROUP--, --ARTIFACT--, --VERSION--);
      KieFileSystem kieFileSystem = kieServices.newKieFileSystem().generateAndWritePomXML(releaseId);
      Set<Rule> rules = ... we gather all our rules ...
      ... add all those rules to kieFileSystem
      
      KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem);
      kieBuilder.buildAll();
      
      //We also process the messages produced in this buildAll() but it is not relevant for this matter since there are none.
      

       

      Show
      number Step Clarification 1 Drools KieContainer fully operational it needs to have at least one session that has been fired all rules 2 Create a rule and add it to the container the rule will use an interface that has never been used before in any rule 3 Create a new KieSession in that container   4 Fire all rules in that container   5 Check if the rule has been triggered the rule does not trigger at all 6 Reload the whole container   7 Fire the rules again the process of firing the rules is exactly the same as in the previous steps 8 Check if the rule has been triggered the rule is triggered and works exactly as expected   How the rule is added to the container Resource resource = newRule.toResource(); kieFileSystem.write(resource); IncrementalResults results = ((InternalKieBuilder) kieBuilder).incrementalBuild(); ... process results which does not affect the container ... kieContainer.updateToVersion(releaseId); Summary of the rule itself package ... some imports... rule "rule-name" when     $something : SomeInteface(property1 == "some value" , property2 == "some other value" ); then     some logic to write a message     end What I mean when I say "reload the container" KieServices kieServices = KieServices.Factory.get(); ReleaseId releaseId = kieServices.newReleaseId(--GROUP--, --ARTIFACT--, --VERSION--); KieFileSystem kieFileSystem = kieServices.newKieFileSystem().generateAndWritePomXML(releaseId); Set<Rule> rules = ... we gather all our rules ... ... add all those rules to kieFileSystem KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem); kieBuilder.buildAll(); //We also process the messages produced in this buildAll() but it is not relevant for this matter since there are none.  
    • Workaround Exists
    • Hide

      This is a possible solution I found while debugging drools, but I have not checked if it has any impact on other scenarios or in engine's performance.

      The solution would be to prevent ObjectTypeConf from returning or even having an empty array as objectTypeNodes. Which could be solved, again I do not know which side effects this could have, method getObjectTypeNodes():

      public ObjectTypeNode[] getObjectTypeNodes() {
          if ( this.objectTypeNodes == null ) {
              this.objectTypeNodes = getMatchingObjectTypes( this.cls );
          }
          return this.objectTypeNodes;
      }

      for this:

       

      public ObjectTypeNode[] getObjectTypeNodes() {
          if ( this.objectTypeNodes == null || this.objectTypeNodes.length == 0) {
              this.objectTypeNodes = getMatchingObjectTypes( this.cls );
          }
          return this.objectTypeNodes;
      }

      PD: In my case this.objectTypeNodes was an empty array.

      Show
      This is a possible solution I found while debugging drools, but I have not checked if it has any impact on other scenarios or in engine's performance. The solution would be to prevent ObjectTypeConf from returning or even having an empty array as objectTypeNodes. Which could be solved, again I do not know which side effects this could have, method getObjectTypeNodes() : public ObjectTypeNode[] getObjectTypeNodes() {     if ( this .objectTypeNodes == null ) {         this .objectTypeNodes = getMatchingObjectTypes( this .cls );     }     return this .objectTypeNodes; } for this:   public ObjectTypeNode[] getObjectTypeNodes() {     if ( this .objectTypeNodes == null || this .objectTypeNodes.length == 0) {         this .objectTypeNodes = getMatchingObjectTypes( this .cls );     }     return this .objectTypeNodes; } PD: In my case this.objectTypeNodes was an empty array.
    • NEW
    • NEW
    • 75248219
    • ---
    • ---

    Description

      OPTIONS

      I am not sure what a solution could be, since thee are multiple options, I might be wrong of course.

      1. Make cache types programmatically accessible so that, through a builder, we could add type mappings to it
      2. Prevent mappings from being an empty array (maybe this can have a performance impact)

      SUMMARY

      When I add a rule to a container that has active sessions, and it uses an interface that has never been used in any rule before in the container, that rule is never triggered. But if I reload the whole container that same rule is triggered and works properly.

       

      Drools, possible, bug

      It is possible that I made a mistake and therefore that is why it isn't working, and is also why I posted this issue in Stackoverflow prior to come directly to drools ticketing.

      Take into account that in this example (you can check on the steps to reproduce everything is detailed with code examples):

      adding a rule -> write in KieFileSystem and then incremental build

      reloading the container -> buildAll on the given container

       

      When drools agenda needs to decide whether a given rule should be triggered, it uses a class type cache. And my problem is that the cache itself does not contain the same data if I add the rule or if I reload the whole container.

      Differences between just adding a rule or reloading the container

      Bot examples comes from class ClassObjectTypeConf

      getObjectTypeNodes()

      public ObjectTypeNode[] getObjectTypeNodes() {
          if ( this.objectTypeNodes == null ) {
              this.objectTypeNodes = getMatchingObjectTypes( this.cls );
          }
          return this.objectTypeNodes;
      }

      getMatchingObjectTypes(final Class<?> clazz)

      private ObjectTypeNode[] getMatchingObjectTypes(final Class<?> clazz) {
          final List<ObjectTypeNode> cache = new ArrayList<ObjectTypeNode>();
          for ( ObjectTypeNode node : kBase.getRete().getObjectTypeNodes( this.entryPoint ).values() ) {
              if ( clazz == DroolsQuery.class ) {
                  // for query objects only add direct matches
                  if ( ((ClassObjectType)node.getObjectType()).getClassType() == clazz ) {
                      cache.add( node );    
                  }
              } else if ( node.isAssignableFrom( new ClassObjectType( clazz ) ) ) {
                  cache.add( node );
              }
          }
          Collections.sort(cache, OBJECT_TYPE_NODE_COMPARATOR);
          return cache.toArray( new ObjectTypeNode[cache.size()] );
      }
      

       

      The difference is that when  I fire the rules in the fourth step the method getMatchingObjectTypes(final Class<?> clazz) is never invoked since this.objectTypeNodes in getObjectTypeNodes() is an empty array instead of null.
      But in the seventh step in the same line of code this.objectTypeNodes is null and therefore getMatchingObjectTypes(final Class<?> clazz) is invoked and the cache is updated for the given entrypoint. 

      Thanks to this update the agenda will be able to "know who SomeInterface is" and therefore trigger the rule.

      Of course if we are in the fourth step and, instead of allowing the flow to go normally, we change this.objectTypeNodes to null (basically forcing drools to restart the cache for this entrypoint) everything will work smoothly. 

      Attachments

        Activity

          People

            mfusco@redhat.com Mario Fusco
            dfarras Diego Farras (Inactive)
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

            Dates

              Created:
              Updated:
              Resolved: