Hello again,<br><br>Recently while continuing to work on a complex event processing project I have encountered strange,<br>but interesting, situations where the fireAllRules() method of a StatefulKnowledgeSession appears to<br>
return before actually emptying the activations on the agenda. I was under the impression that this <br>method only returns after all activations, including subsequent activations, have been completed and<br>removed from the agenda. If I'm incorrect then feel free to disregard the rest of this message.<br>
<br>[Drools v 5.0.1, JUnit 4.0, Spring 2.5.6]<br><br>The behavior I have noticed begins when working with a rule such as "Find Missing Input Events" <br>(in file test.drl):<br>------------------------------------------------------------------------<br>
package org.drools.test<br><br>import org.drools.test.*<br><br>declare Event<br> @role( event )<br> @timestamp( beginning )<br> @duration( length )<br> @expires( 1h )<br>end<br><br>declare Fault<br> @role( event )<br>
@timestamp( beginning )<br> @duration( length )<br> @expires( 1h )<br>end<br><br>query "getFaults"<br> $fault: Fault() from entry-point "faults"<br>end<br><br>rule "Find Missing Input Events"<br>
when<br> $a: Event()<br> from entry-point "input"<br> $b: Event( this after $a)<br> from entry-point "input"<br> not (Event( (this after $a || this metby $a), (this before $b || this meets $b))<br>
from entry-point "input")<br> not (Fault(this metby $a, this meets $b)<br> from entry-point "faults")<br> then<br> entryPoints["faults"].insert(new Fault($a, $b));<br>
end<br><br>rule "Print Faults"<br><br> when<br> $f: Fault() from entry-point "faults"<br> then<br> System.out.println($f);<br>end<br>------------------------------------------------------------------------<br>
<br>The purpose of this rule is to detect when one or more events are missing in an event sequence. The<br>events in question are supposed to continue from one to the next without any time in-between. If this <br>rule does discover a gap/absence between two events a Fault is created for further processing. (*Note <br>
that the Event and Fault classes that I used only contain two fields, beginning and length, to be used as<br>the time-stamp and duration respectively.) I noticed the unexpected behavior shortly after creating this <br>unit test:<br>
<br>------------------------------------------------------------------------<br>public class FireAllRulesTest {<br><br> KnowledgeBase knowledgeBase;<br> <br> StatefulKnowledgeSession session;<br> <br> /**<br>
* @throws java.lang.Exception<br> */<br> @Before<br> public void setUp() throws Exception {<br> KnowledgeBuilder builder = KnowledgeBuilderFactory.newKnowledgeBuilder();<br> builder.add(ResourceFactory.newFileResource("target/test-classes/test.drl"), ResourceType.DRL);<br>
<br> if(builder.hasErrors()){<br> throw new RuntimeException(builder.getErrors().toString());<br> }<br> KnowledgeBaseConfiguration configuration = KnowledgeBaseFactory.newKnowledgeBaseConfiguration();<br>
configuration.setOption(EventProcessingOption.STREAM);<br> <br> knowledgeBase = KnowledgeBaseFactory.newKnowledgeBase(configuration);<br> knowledgeBase.addKnowledgePackages(builder.getKnowledgePackages());<br>
<br> session = knowledgeBase.newStatefulKnowledgeSession();<br> WorkingMemoryConsoleLogger logger = new WorkingMemoryConsoleLogger(session);<br> }<br> <br> @Test<br> public void testAbsence(){<br>
System.out.println("Running...");<br> //Record the rules that have fired.<br> ActivationTrackingEventListener listener = new ActivationTrackingEventListener();<br> session.addEventListener(listener);<br>
<br> //Start at 0, go to 100.<br> Event first = new Event(0,100);<br> <br> //Start at 1000, go to 1100. Absence of 900.<br> Event second = new Event(1000, 100);<br> <br> //Start at 1100, go to 1200. No absence.<br>
Event third = new Event(1100, 100);<br> <br> //Start at 1200, go to 1300. No absence.<br> Event fourth = new Event(1200, 100);<br><br> //Start at 2000, go to 2100. Absence of 700.<br>
Event fifth = new Event(2000, 100);<br> <br> WorkingMemoryEntryPoint ingest = session.getWorkingMemoryEntryPoint("input");<br> ingest.insert(first);<br> ingest.insert(second);<br>
ingest.insert(third);<br> ingest.insert(fourth); <br> ingest.insert(fifth);<br> <br> System.out.println("All events have been inserted.");<br><br> session.fireAllRules();<br>
<br> System.out.println("Rules have finished firing.");<br><br> //Expect that "Find Missing Input Events" has fired and that there are two faults as a result.<br> assertTrue(listener.hasRuleFired("Find Missing Input Events"));<br>
assertEquals(2,faultCount());<br> <br> System.out.println("Test completed.");<br> }<br> <br> public int faultCount(){<br> QueryResults results = session.getQueryResults("getFaults");<br>
return results.size();<br> }<br><br>}<br>------------------------------------------------------------------------<br><br>The typical result of this test is a failure about 90% of the time. Strangely, the other 10% of the time this exact <br>
same test is run it will pass, without any modifications. Equally interesting is the output of the WorkingMemoryLogger:<br><br>------------------------------------------------------------------------<br>Running...<br>OBJECT ASSERTED value:org.drools.test.Event@1b994de factId: 1<br>
BEFORE ACTIVATION FIRED rule:Find Missing Input Events activationId:Find Missing Input Events [2, 1, 0] <br> declarations: $b=org.drools.test.Event@2803d5(2); $a=org.drools.test.Event@1b994de(1)<br>ACTIVATION CREATED rule:Find Missing Input Events activationId:Find Missing Input Events [2, 1, 0] <br>
declarations: $b=org.drools.test.Event@2803d5(2); $a=org.drools.test.Event@1b994de(1)<br>OBJECT ASSERTED value:org.drools.test.Event@2803d5 factId: 2<br>OBJECT ASSERTED value:org.drools.test.Event@1b32627 factId: 3<br>
OBJECT ASSERTED value:org.drools.test.Event@ad157f factId: 4<br>ACTIVATION CREATED rule:Find Missing Input Events activationId:Find Missing Input Events [5, 4, 0] <br> declarations: $b=org.drools.test.Event@1bfa3d3(5); $a=org.drools.test.Event@ad157f(4)<br>
OBJECT ASSERTED value:org.drools.test.Event@1bfa3d3 factId: 5<br>All events have been inserted.<br>***Rules have finished firing.<br>ACTIVATION CREATED rule:Print Faults activationId:Print Faults [6, 0] declarations: $f=org.drools.test.Fault@159780d(6)<br>
OBJECT ASSERTED value:org.drools.test.Fault@159780d factId: 6<br>AFTER ACTIVATION FIRED rule:Find Missing Input Events activationId:Find Missing Input Events [2, 1, 0] <br> declarations: $b=org.drools.test.Event@2803d5(2); $a=org.drools.test.Event@1b994de(1)<br>
BEFORE ACTIVATION FIRED rule:Print Faults activationId:Print Faults [6, 0] declarations: $f=org.drools.test.Fault@159780d(6)<br>org.drools.test.Fault@159780d<br>AFTER ACTIVATION FIRED rule:Print Faults activationId:Print Faults [6, 0] declarations: $f=org.drools.test.Fault@159780d(6)<br>
BEFORE ACTIVATION FIRED rule:Find Missing Input Events activationId:Find Missing Input Events [5, 4, 0] <br> declarations: $b=org.drools.test.Event@1bfa3d3(5); $a=org.drools.test.Event@ad157f(4)<br>ACTIVATION CREATED rule:Print Faults activationId:Print Faults [7, 0] declarations: $f=org.drools.test.Fault@c26b16(7)<br>
OBJECT ASSERTED value:org.drools.test.Fault@c26b16 factId: 7<br>AFTER ACTIVATION FIRED rule:Find Missing Input Events activationId:Find Missing Input Events [5, 4, 0] <br> declarations: $b=org.drools.test.Event@1bfa3d3(5); $a=org.drools.test.Event@ad157f(4)<br>
BEFORE ACTIVATION FIRED rule:Print Faults activationId:Print Faults [7, 0] declarations: $f=org.drools.test.Fault@c26b16(7)<br>org.drools.test.Fault@c26b16<br>AFTER ACTIVATION FIRED rule:Print Faults activationId:Print Faults [7, 0] declarations: $f=org.drools.test.Fault@c26b16(7)<br>
------------------------------------------------------------------------<br><br>It seems that the rule is indeed firing correctly, but that it is doing so only after returning from fireAllRules method. <br>I have marked line that follows fireAllRules() with *** above. So the rule fires, but the test fails because the firing <br>
takes place after the junit assertions are checked.<br><br>But that's not all, it gets more interesting. Looking back at the rule file, when you comment out the last condition:<br><br> not (Fault(this metby $a, this meets $b)<br>
from entry-point "faults")<br><br>The problem with the unit test disappears. After much thought I'm still not sure why this is, but the last condition is<br>critical to avoid creating redundant Faults (Because a lot of the Events are redundant as well. It just comes with the<br>
problem being solved). The problem also disappears if you put a Thread.sleep(1000) after firing the rules, which is<br>why I suspect a timing issue between the rules firing and the unit test assertions being evaluated. This could explain<br>
why the test sometimes works, and sometimes doesn't.<br><br>I'm hopeful that someone will understand why this is happening, and point how how to prevent it. Thank you in advance.<br><br>Bill<br><br>