I found a much easier way to recreate the problem described in my previous post. I believe I have also found the root cause of the problem. Below are the steps to recreate the issue:
- Create a simple ruleflow and rules and compile them into a rule package.
- In code create a stateless session
- In a loop
o Create a fact
o Create a new execution batch with the following commands
- Insert the fact from above
- Start Process
- Fire All Rules
- Get Objects
o Execute the batch
o Check the results (number of rules that ran)
I posted before that the problem didn’t happen while executing locally (not in drools server). I didn’t realize this before, but adding a loop that executes the rules multiple times with the same session recreates the problem.
Below is code that will recreate the issue. Notice that the code below creates one instance of ksession and reuses it in the loop.
//Setup
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
//The package includes the ruleflow
kbuilder.add( ResourceFactory.newFileResource( ("YourRulePackage_WithRuleflow.pkg") ), ResourceType.PKG );
KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();
kbase.addKnowledgePackages( kbuilder.getKnowledgePackages() );
//Create the stateless knowledge session outside the for loop
StatelessKnowledgeSession ksession = kbase.newStatelessKnowledgeSession(); //Loop through executing the rules with the same data 3 times
for (int x = 0; x < 3; x++)
{
//Create a new instance of the input fact
FactType inputDataType = kbase.getFactType("SimpleRuleflowTest", "Input");
Object inputData = inputDataType.newInstance();
inputDataType.set(inputData, "Name", "Test data");
//Create a new instance of the command list
List cmds = new ArrayList();
cmds.add( CommandFactory.newInsert( inputData ));
cmds.add( CommandFactory.newStartProcess( "TestRuleflow"));
cmds.add( CommandFactory.newFireAllRules("rules"));
cmds.add( CommandFactory.newGetObjects("output"));
//Execute the rules
ExecutionResults results = ksession.execute( CommandFactory.newBatchExecution( cmds ) );
//Get the number of rules that ran
Object rules = results.getValue("rules");
System.out.println("Rules that ran: " + rules.toString());
}
Each iteration through the loop should fire 3 rules. Running the code above you should get the following output:
Rules that ran: 3
Rules that ran: 1
Rules that ran: 1
I spent several hours researching the drools source code and I believe I have found the problem. What I have found is related to
Issue 2718.
Check out the change made for this issue:
2718 fix.
Below is a snippet from the code that was modified for issue 2718. The “if (!initialized)” block was added. The code loops through event listeners on the wm object and adds them to the stateless session’s listeners. This works fine for the first rule execution. But the next rule execution creates a new instance for wm. Therefore it also creates new instances of listeners for wm. So we have a new instance of wm and the stateless session is pointing to the old listeners from the first instance of wm. I believe that is why it only runs correctly one time. It seems that that the “if (!initialized)” block of code needs to execute every time newWorkingMemory() is called.
public StatefulKnowledgeSession newWorkingMemory() {
:
:
ReteooWorkingMemory wm = new ReteooWorkingMemory( this.ruleBase.nextWorkingMemoryCounter(),
this.ruleBase,
(SessionConfiguration) this.conf,
this.environment );
:
:
if (!initialized) { // copy over the default generated listeners that are used for internal stuff once
for (org.drools.event.AgendaEventListener listener: wm.getAgendaEventSupport().getEventListeners()) {
this.agendaEventSupport.addEventListener(listener);
}
for (org.drools.event.WorkingMemoryEventListener listener: wm.getWorkingMemoryEventSupport().getEventListeners()) {
this.workingMemoryEventSupport.addEventListener(listener);
}
InternalProcessRuntime processRuntime = wm.getProcessRuntime();
if (processRuntime != null) {
for (ProcessEventListener listener: processRuntime.getProcessEventListeners()) {
this.processEventSupport.addEventListener(listener);
}
}
initialized = true;
}
:
:
}
So, I tested this theory in my test function. I used reflection to flip the “initialized” flag at the bottom of my loop. I did this to force the listeners of the stateless session to be refreshed for every execute call. It worked! The other nodes in the ruleflow now get executed after the first rule execution. Below is a snippet of my modified test code:
//Same As Above
:
:
//Create the stateless knowledge session outside the for loop
StatelessKnowledgeSession ksession = kbase.newStatelessKnowledgeSession();
//Loop through executing the rules with the same data 3 times
for (int x = 0; x < 3; x++)
{
:
:
//Flip the initialized flag to false through reflection
Field field = StatelessKnowledgeSessionImpl.class.getDeclaredField("initialized");
field.setAccessible(true);
field.setBoolean(ksession, false); }
Running the code above you should get the following output:
Rules that ran: 3
Rules that ran: 3
Rules that ran: 3
My original problem deals with drools-server. It isn’t possible for me to flip the initialized flag to false in that scenario. So, is there any sort of workaround that would get me past this issue when using drools-server?