Uploaded image for project: 'JBRULES'
  1. JBRULES
  2. JBRULES-3320

Stateful memory corruption for repetitive updates on the same facts

This issue belongs to an archived project. You can view it, but you can't modify it. Learn more

    XMLWordPrintable

Details

    • Hide

      package com.sample;

      import static org.junit.Assert.assertEquals;

      import java.io.Reader;
      import java.io.StringReader;
      import java.util.ArrayList;
      import java.util.List;

      import org.drools.KnowledgeBase;
      import org.drools.KnowledgeBaseFactory;
      import org.drools.builder.KnowledgeBuilder;
      import org.drools.builder.KnowledgeBuilderError;
      import org.drools.builder.KnowledgeBuilderErrors;
      import org.drools.builder.KnowledgeBuilderFactory;
      import org.drools.builder.ResourceType;
      import org.drools.io.ResourceFactory;
      import org.drools.logger.KnowledgeRuntimeLogger;
      import org.drools.logger.KnowledgeRuntimeLoggerFactory;
      import org.drools.runtime.StatefulKnowledgeSession;
      import org.drools.runtime.rule.FactHandle;
      import org.junit.Test;

      /**

      • This is a sample class to launch a rule.
        */
        public class DroolsTest {
        List notCovered = new ArrayList();
        List partiallyCovered = new ArrayList();
        List totallyCovered = new ArrayList();

      @Test
      public void testExecute() throws Exception

      { // load up the knowledge base KnowledgeBase kbase = readKnowledgeBase(); StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession(); ksession.setGlobal("totallyCovered", totallyCovered); ksession.setGlobal("partiallyCovered", partiallyCovered); ksession.setGlobal("notCovered", notCovered); KnowledgeRuntimeLogger logger = KnowledgeRuntimeLoggerFactory.newFileLogger(ksession, "test"); // Staffing required at interval 100 IntervalRequirement ir100 = new IntervalRequirement(100, 2); ksession.insert(ir100); // Staffing required at interval 101 IntervalRequirement ir101 = new IntervalRequirement(101, 2); ksession.insert(ir101); // Staffing required at interval 102 IntervalRequirement ir102 = new IntervalRequirement(102, 2); ksession.insert(ir102); // Staffing required at interval 103 IntervalRequirement ir103 = new IntervalRequirement(103, 2); ksession.insert(ir103); ShiftAssignment sa = new ShiftAssignment(); sa.setShiftStartTime(100); FactHandle saHandle = null; // Covers 1 interval clearGlobals(); sa.setShiftEndTime(101); System.out.println("ShiftAssignment set from " + sa.getShiftStartTime() + " to " + sa.getShiftEndTime()); saHandle = ksession.insert(sa); ksession.fireAllRules(); assertEquals("notCovered with " + sa, 3, notCovered.size()); assertEquals("totallyCovered with " + sa, 0, totallyCovered.size()); assertEquals("partiallyCovered with " + sa, 1, partiallyCovered.size()); // Covers 3 intervals clearGlobals(); sa.setShiftEndTime(103); System.out.println("ShiftAssignment set from " + sa.getShiftStartTime() + " to " + sa.getShiftEndTime()); ksession.update(saHandle, sa); ksession.fireAllRules(); assertEquals("notCovered with " + sa, 1, notCovered.size()); assertEquals("totallyCovered with " + sa, 0, totallyCovered.size()); assertEquals("partiallyCovered with " + sa, 3, partiallyCovered.size()); // Covers 2 intervals clearGlobals(); sa.setShiftEndTime(102); System.out.println("ShiftAssignment set from " + sa.getShiftStartTime() + " to " + sa.getShiftEndTime()); ksession.update(saHandle, sa); ksession.fireAllRules(); assertEquals("notCovered with " + sa, 2, notCovered.size()); assertEquals("totallyCovered with " + sa, 0, totallyCovered.size()); assertEquals("partiallyCovered with " + sa, 2, partiallyCovered.size()); // Covers 4 intervals clearGlobals(); sa.setShiftEndTime(104); System.out.println("ShiftAssignment set from " + sa.getShiftStartTime() + " to " + sa.getShiftEndTime()); ksession.update(saHandle, sa); ksession.fireAllRules(); assertEquals("notCovered with " + sa, 0, notCovered.size()); assertEquals("totallyCovered with " + sa, 0, totallyCovered.size()); assertEquals("partiallyCovered with " + sa, 4, partiallyCovered.size()); // Covers 1 interval clearGlobals(); sa.setShiftEndTime(101); System.out.println("ShiftAssignment set from " + sa.getShiftStartTime() + " to " + sa.getShiftEndTime()); ksession.update(saHandle, sa); ksession.fireAllRules(); assertEquals("notCovered with " + sa, 3, notCovered.size()); assertEquals("totallyCovered with " + sa, 0, totallyCovered.size()); assertEquals("partiallyCovered with " + sa, 1, partiallyCovered.size()); ksession.dispose(); logger.close(); }

      private void clearGlobals()

      { totallyCovered.clear(); partiallyCovered.clear(); notCovered.clear(); }

      private static KnowledgeBase readKnowledgeBase() throws Exception {
      KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
      Reader drlSr = new StringReader(DRL);
      kbuilder.add(ResourceFactory.newReaderResource(drlSr), ResourceType.DRL);
      KnowledgeBuilderErrors errors = kbuilder.getErrors();
      if (errors.size() > 0) {
      for (KnowledgeBuilderError error: errors)

      { System.err.println(error); }

      throw new IllegalArgumentException("Could not parse knowledge: " + errors);
      }
      KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();
      kbase.addKnowledgePackages(kbuilder.getKnowledgePackages());
      return kbase;
      }

      public class IntervalRequirement
      {
      private int interval;
      private int staffingRequired;

      public IntervalRequirement(int interval, int staffingRequired)

      { super(); this.interval = interval; this.staffingRequired = staffingRequired; }

      public int getInterval()

      { return interval; }

      public int getStaffingRequired()

      { return staffingRequired; }

      @Override
      public String toString()

      { StringBuilder sb = new StringBuilder(); sb.append(getClass().getSimpleName()).append(": ") .append("interval: ").append(this.interval) .append(", staffingRequired: ").append(this.staffingRequired) ; return sb.toString(); }

      }

      public class ShiftAssignment
      {
      private int shiftStartTime = -1;
      private int shiftEndTime = -1;

      public ShiftAssignment() {
      }

      public int getShiftStartTime()

      { return this.shiftStartTime; }

      public int getShiftEndTime()

      { return this.shiftEndTime; }

      public void setShiftStartTime(int shiftStartTime)

      { this.shiftStartTime = shiftStartTime; }

      public void setShiftEndTime(int shiftEndTime)

      { this.shiftEndTime = shiftEndTime; }

      @Override
      public String toString()

      { StringBuilder sb = new StringBuilder(); sb.append("ShiftAssignment: ") .append(" ") .append("st: ").append(this.shiftStartTime).append(" end: ").append(this.shiftEndTime); return sb.toString(); }

      }

      private static final String DRL =
      "package com.lfsoscience.planner.solver \n"
      + "dialect \"java\" \n"
      + " \n"
      + "import java.util.ArrayList; \n"
      + "import java.util.HashSet; \n"
      + " \n"
      + "import com.sample.DroolsTest.ShiftAssignment; \n"
      + "import com.sample.DroolsTest.IntervalRequirement; \n"
      + " \n"
      + "global java.util.List totallyCovered \n"
      + "global java.util.List notCovered \n"
      + "global java.util.List partiallyCovered \n"
      + " \n"
      + "rule \"intervalRequirementNotCovered\" \n"
      + " when \n"
      + " $intervalReq : IntervalRequirement($interval : interval) \n"
      + " not (ShiftAssignment(shiftStartTime <= $interval, shiftEndTime > $interval)) \n"
      + " then \n"
      + " #actions \n"
      + " notCovered.add($intervalReq); \n"
      + " System.out.println(\"intervalRequirementNotCovered -> \" + $intervalReq); \n"
      + "end \n"
      + " \n"
      + "rule \"intervalRequirementPartiallyCovered\" \n"
      + " when \n"
      + " $intervalReq : IntervalRequirement($interval : interval, $staffingRequired : staffingRequired) \n"
      + " $matchingShiftAssignmentsCount : Number(intValue() > 0, intValue() < $staffingRequired) from accumulate( \n"
      + " $matchingShiftAssignments : ShiftAssignment(shiftStartTime <= $interval, shiftEndTime > $interval) \n"
      + " , count($matchingShiftAssignments) \n"
      + " ) \n"
      + " then \n"
      + " #actions \n"
      + " partiallyCovered.add($intervalReq); \n"
      + " System.out.println(\"intervalRequirementPartiallyCovered -> \" + $intervalReq + \" covered by \" + $matchingShiftAssignmentsCount + \" ShiftAssignment(s)\"); \n"
      + "end \n"
      + " \n"
      + "rule \"intervalRequirementTotallyCovered\" \n"
      + " when \n"
      + " $intervalReq : IntervalRequirement($interval : interval, $staffingRequired : staffingRequired) \n"
      + " $matchingShiftAssignmentsCount : Number(intValue() > 0, intValue() >= $staffingRequired) from accumulate( \n"
      + " $matchingShiftAssignments : ShiftAssignment(shiftStartTime <= $interval, shiftEndTime > $interval) \n"
      + " , count($matchingShiftAssignments) \n"
      + " ) \n"
      + " then \n"
      + " #actions \n"
      + " totallyCovered.add($intervalReq); \n"
      + " System.out.println(\"intervalRequirementTotallyCovered -> \" + $intervalReq + \" covered by \" + $matchingShiftAssignmentsCount + \" ShiftAssignment(s)\"); \n"
      + "end \n"
      + " \n"
      ;
      }

      Show
      package com.sample; import static org.junit.Assert.assertEquals; import java.io.Reader; import java.io.StringReader; import java.util.ArrayList; import java.util.List; import org.drools.KnowledgeBase; import org.drools.KnowledgeBaseFactory; import org.drools.builder.KnowledgeBuilder; import org.drools.builder.KnowledgeBuilderError; import org.drools.builder.KnowledgeBuilderErrors; import org.drools.builder.KnowledgeBuilderFactory; import org.drools.builder.ResourceType; import org.drools.io.ResourceFactory; import org.drools.logger.KnowledgeRuntimeLogger; import org.drools.logger.KnowledgeRuntimeLoggerFactory; import org.drools.runtime.StatefulKnowledgeSession; import org.drools.runtime.rule.FactHandle; import org.junit.Test; /** This is a sample class to launch a rule. */ public class DroolsTest { List notCovered = new ArrayList(); List partiallyCovered = new ArrayList(); List totallyCovered = new ArrayList(); @Test public void testExecute() throws Exception { // load up the knowledge base KnowledgeBase kbase = readKnowledgeBase(); StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession(); ksession.setGlobal("totallyCovered", totallyCovered); ksession.setGlobal("partiallyCovered", partiallyCovered); ksession.setGlobal("notCovered", notCovered); KnowledgeRuntimeLogger logger = KnowledgeRuntimeLoggerFactory.newFileLogger(ksession, "test"); // Staffing required at interval 100 IntervalRequirement ir100 = new IntervalRequirement(100, 2); ksession.insert(ir100); // Staffing required at interval 101 IntervalRequirement ir101 = new IntervalRequirement(101, 2); ksession.insert(ir101); // Staffing required at interval 102 IntervalRequirement ir102 = new IntervalRequirement(102, 2); ksession.insert(ir102); // Staffing required at interval 103 IntervalRequirement ir103 = new IntervalRequirement(103, 2); ksession.insert(ir103); ShiftAssignment sa = new ShiftAssignment(); sa.setShiftStartTime(100); FactHandle saHandle = null; // Covers 1 interval clearGlobals(); sa.setShiftEndTime(101); System.out.println("ShiftAssignment set from " + sa.getShiftStartTime() + " to " + sa.getShiftEndTime()); saHandle = ksession.insert(sa); ksession.fireAllRules(); assertEquals("notCovered with " + sa, 3, notCovered.size()); assertEquals("totallyCovered with " + sa, 0, totallyCovered.size()); assertEquals("partiallyCovered with " + sa, 1, partiallyCovered.size()); // Covers 3 intervals clearGlobals(); sa.setShiftEndTime(103); System.out.println("ShiftAssignment set from " + sa.getShiftStartTime() + " to " + sa.getShiftEndTime()); ksession.update(saHandle, sa); ksession.fireAllRules(); assertEquals("notCovered with " + sa, 1, notCovered.size()); assertEquals("totallyCovered with " + sa, 0, totallyCovered.size()); assertEquals("partiallyCovered with " + sa, 3, partiallyCovered.size()); // Covers 2 intervals clearGlobals(); sa.setShiftEndTime(102); System.out.println("ShiftAssignment set from " + sa.getShiftStartTime() + " to " + sa.getShiftEndTime()); ksession.update(saHandle, sa); ksession.fireAllRules(); assertEquals("notCovered with " + sa, 2, notCovered.size()); assertEquals("totallyCovered with " + sa, 0, totallyCovered.size()); assertEquals("partiallyCovered with " + sa, 2, partiallyCovered.size()); // Covers 4 intervals clearGlobals(); sa.setShiftEndTime(104); System.out.println("ShiftAssignment set from " + sa.getShiftStartTime() + " to " + sa.getShiftEndTime()); ksession.update(saHandle, sa); ksession.fireAllRules(); assertEquals("notCovered with " + sa, 0, notCovered.size()); assertEquals("totallyCovered with " + sa, 0, totallyCovered.size()); assertEquals("partiallyCovered with " + sa, 4, partiallyCovered.size()); // Covers 1 interval clearGlobals(); sa.setShiftEndTime(101); System.out.println("ShiftAssignment set from " + sa.getShiftStartTime() + " to " + sa.getShiftEndTime()); ksession.update(saHandle, sa); ksession.fireAllRules(); assertEquals("notCovered with " + sa, 3, notCovered.size()); assertEquals("totallyCovered with " + sa, 0, totallyCovered.size()); assertEquals("partiallyCovered with " + sa, 1, partiallyCovered.size()); ksession.dispose(); logger.close(); } private void clearGlobals() { totallyCovered.clear(); partiallyCovered.clear(); notCovered.clear(); } private static KnowledgeBase readKnowledgeBase() throws Exception { KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder(); Reader drlSr = new StringReader(DRL); kbuilder.add(ResourceFactory.newReaderResource(drlSr), ResourceType.DRL); KnowledgeBuilderErrors errors = kbuilder.getErrors(); if (errors.size() > 0) { for (KnowledgeBuilderError error: errors) { System.err.println(error); } throw new IllegalArgumentException("Could not parse knowledge: " + errors); } KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase(); kbase.addKnowledgePackages(kbuilder.getKnowledgePackages()); return kbase; } public class IntervalRequirement { private int interval; private int staffingRequired; public IntervalRequirement(int interval, int staffingRequired) { super(); this.interval = interval; this.staffingRequired = staffingRequired; } public int getInterval() { return interval; } public int getStaffingRequired() { return staffingRequired; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(getClass().getSimpleName()).append(": ") .append("interval: ").append(this.interval) .append(", staffingRequired: ").append(this.staffingRequired) ; return sb.toString(); } } public class ShiftAssignment { private int shiftStartTime = -1; private int shiftEndTime = -1; public ShiftAssignment() { } public int getShiftStartTime() { return this.shiftStartTime; } public int getShiftEndTime() { return this.shiftEndTime; } public void setShiftStartTime(int shiftStartTime) { this.shiftStartTime = shiftStartTime; } public void setShiftEndTime(int shiftEndTime) { this.shiftEndTime = shiftEndTime; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("ShiftAssignment: ") .append(" ") .append("st: ").append(this.shiftStartTime).append(" end: ").append(this.shiftEndTime); return sb.toString(); } } private static final String DRL = "package com.lfsoscience.planner.solver \n" + "dialect \"java\" \n" + " \n" + "import java.util.ArrayList; \n" + "import java.util.HashSet; \n" + " \n" + "import com.sample.DroolsTest.ShiftAssignment; \n" + "import com.sample.DroolsTest.IntervalRequirement; \n" + " \n" + "global java.util.List totallyCovered \n" + "global java.util.List notCovered \n" + "global java.util.List partiallyCovered \n" + " \n" + "rule \"intervalRequirementNotCovered\" \n" + " when \n" + " $intervalReq : IntervalRequirement($interval : interval) \n" + " not (ShiftAssignment(shiftStartTime <= $interval, shiftEndTime > $interval)) \n" + " then \n" + " #actions \n" + " notCovered.add($intervalReq); \n" + " System.out.println(\"intervalRequirementNotCovered -> \" + $intervalReq); \n" + "end \n" + " \n" + "rule \"intervalRequirementPartiallyCovered\" \n" + " when \n" + " $intervalReq : IntervalRequirement($interval : interval, $staffingRequired : staffingRequired) \n" + " $matchingShiftAssignmentsCount : Number(intValue() > 0, intValue() < $staffingRequired) from accumulate( \n" + " $matchingShiftAssignments : ShiftAssignment(shiftStartTime <= $interval, shiftEndTime > $interval) \n" + " , count($matchingShiftAssignments) \n" + " ) \n" + " then \n" + " #actions \n" + " partiallyCovered.add($intervalReq); \n" + " System.out.println(\"intervalRequirementPartiallyCovered -> \" + $intervalReq + \" covered by \" + $matchingShiftAssignmentsCount + \" ShiftAssignment(s)\"); \n" + "end \n" + " \n" + "rule \"intervalRequirementTotallyCovered\" \n" + " when \n" + " $intervalReq : IntervalRequirement($interval : interval, $staffingRequired : staffingRequired) \n" + " $matchingShiftAssignmentsCount : Number(intValue() > 0, intValue() >= $staffingRequired) from accumulate( \n" + " $matchingShiftAssignments : ShiftAssignment(shiftStartTime <= $interval, shiftEndTime > $interval) \n" + " , count($matchingShiftAssignments) \n" + " ) \n" + " then \n" + " #actions \n" + " totallyCovered.add($intervalReq); \n" + " System.out.println(\"intervalRequirementTotallyCovered -> \" + $intervalReq + \" covered by \" + $matchingShiftAssignmentsCount + \" ShiftAssignment(s)\"); \n" + "end \n" + " \n" ; }

    Description

      Multiple updates to the same fact does not yield the expected results (similar to JBRULES-2809 issue)

      The provided UT (see 'steps to reproduce' section) makes use of 2 fact instances: one IntervalRequirement (never updated) and one ShiftAssignment.
      The same ShiftAssignment will be updated multiple times to cover one or more IntervalRequirement(s).

      Here is the output of the test without executing the assertions. For each update I have added comments explaining what the expected results should be:

      Update #1: ShiftAssignment set from 100 to 101
      intervalRequirementPartiallyCovered -> IntervalRequirement: interval: 100, staffingRequired: 2 covered by 1 ShiftAssignment(s)
      intervalRequirementNotCovered -> IntervalRequirement: interval: 103, staffingRequired: 2
      intervalRequirementNotCovered -> IntervalRequirement: interval: 102, staffingRequired: 2
      intervalRequirementNotCovered -> IntervalRequirement: interval: 101, staffingRequired: 2
      COMMENTS: correct
      Update #2:ShiftAssignment set from 100 to 103
      intervalRequirementPartiallyCovered -> IntervalRequirement: interval: 101, staffingRequired: 2 covered by 1 ShiftAssignment(s)
      intervalRequirementPartiallyCovered -> IntervalRequirement: interval: 102, staffingRequired: 2 covered by 1 ShiftAssignment(s)
      intervalRequirementPartiallyCovered -> IntervalRequirement: interval: 100, staffingRequired: 2 covered by 1 ShiftAssignment(s)
      COMMENTS: intervalRequirementNotCovered rule should have fired for interval 103
      Update #3:ShiftAssignment set from 100 to 102
      intervalRequirementNotCovered -> IntervalRequirement: interval: 102, staffingRequired: 2
      intervalRequirementPartiallyCovered -> IntervalRequirement: interval: 101, staffingRequired: 2 covered by 1 ShiftAssignment(s)
      intervalRequirementPartiallyCovered -> IntervalRequirement: interval: 100, staffingRequired: 2 covered by 1 ShiftAssignment(s)
      COMMENT: intervalRequirementNotCovered rule should have fired for interval 103
      Update #4:ShiftAssignment set from 100 to 104
      intervalRequirementPartiallyCovered -> IntervalRequirement: interval: 102, staffingRequired: 2 covered by 1 ShiftAssignment(s)
      intervalRequirementPartiallyCovered -> IntervalRequirement: interval: 103, staffingRequired: 2 covered by 1 ShiftAssignment(s)
      intervalRequirementPartiallyCovered -> IntervalRequirement: interval: 101, staffingRequired: 2 covered by 1 ShiftAssignment(s)
      intervalRequirementPartiallyCovered -> IntervalRequirement: interval: 100, staffingRequired: 2 covered by 1 ShiftAssignment(s)
      COMMENT: correct
      Update #5:ShiftAssignment set from 100 to 101
      intervalRequirementTotallyCovered -> IntervalRequirement: interval: 100, staffingRequired: 2 covered by 2 ShiftAssignment(s)
      intervalRequirementNotCovered -> IntervalRequirement: interval: 103, staffingRequired: 2
      intervalRequirementNotCovered -> IntervalRequirement: interval: 102, staffingRequired: 2
      intervalRequirementNotCovered -> IntervalRequirement: interval: 101, staffingRequired: 2
      COMMENT: results should have been identical of that of update #1: intervalRequirementPartiallyCovered should have fired for interval 100, instead intervalRequirementTotallyCovered rule fired.

      Attachments

        Activity

          People

            etirelli@redhat.com Edson Tirelli
            guyramirez_jira guy ramirez (Inactive)
            Archiver:
            rhn-support-ceverson Clark Everson

            Dates

              Created:
              Updated:
              Resolved:
              Archived:

              PagerDuty