Saturday, July 31, 2010

Using Multi threading with Drools

Drools is a very popular rule engine. It is based on Rete algorithm. To understand what is a rule engine and how to use it check here. But here I will assume that you have some working knowledge on Drools.

What I am going to discuss in this post is 'how to use multi-threading with Drools'. Below is a snippet which creates a KnowledgeBase from a DRL file.



public void buildKnowledgeBase()
{
    KnowledgeBuilder kbuilder = 
             KnowledgeBuilderFactory.newKnowledgeBuilder();
    kbuilder.add( ResourceFactory.newUrlResource( "file://myrules.drl" ),
    assertFalse( kbuilder.hasErrors() );
    KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();
}

If this method is called from multiple threads I get below exception -



Exception in thread "Thread-60" [Error: incomplete statement: (possible
use of reserved keyword as identifier: )]

[Near : {... globals != empt ....}]

^

[Line: 0, Column: 0]

at org.mvel2.MVELInterpretedRuntime.parseAndExecuteInterpreted(*
MVELInterpretedRuntime.java:153*)

at org.mvel2.MVELInterpretedRuntime.parse(*
MVELInterpretedRuntime.java:44*)

at org.mvel2.MVEL.eval(*MVEL.java:514*)

at org.mvel2.templates.res.IfNode.eval(*IfNode.java:61*)

at org.mvel2.templates.res.TextNode.eval(*TextNode.java:46*)

at org.mvel2.templates.res.TerminalNode.eval(*TerminalNode.java:39*)

at org.mvel2.templates.res.ForEachNode.eval(*ForEachNode.java:116*)

at org.mvel2.templates.res.TextNode.eval(*TextNode.java:46*)

at org.mvel2.templates.res.TerminalNode.eval(*TerminalNode.java:39*)

at org.mvel2.templates.res.IfNode.eval(*IfNode.java:64*)

at org.mvel2.templates.res.TextNode.eval(*TextNode.java:46*)

at org.mvel2.templates.res.ExpressionNode.eval(*
ExpressionNode.java:53*)

at org.mvel2.templates.res.TextNode.eval(*TextNode.java:46*)

at org.mvel2.templates.TemplateRuntime.execute(*
TemplateRuntime.java:195*)

at org.mvel2.templates.TemplateRuntime.execute(*
TemplateRuntime.java:190*)

at org.mvel2.templates.TemplateRuntime.execute(*
TemplateRuntime.java:180*)

at org.mvel2.templates.TemplateRuntime.execute(*
TemplateRuntime.java:169*)

at
org.drools.rule.builder.dialect.java.AbstractJavaRuleBuilder.generatTemplates(
*AbstractJavaRuleBuilder.java:126*)

at org.drools.rule.builder.dialect.java.JavaConsequenceBuilder.build(
*JavaConsequenceBuilder.java:128*)

at org.drools.rule.builder.RuleBuilder.build(*RuleBuilder.java:86*)

at org.drools.compiler.PackageBuilder.addRule(*
PackageBuilder.java:1159*)

at org.drools.compiler.PackageBuilder.addPackage(*
PackageBuilder.java:649*)

at org.drools.compiler.PackageBuilder.addPackageFromDrl(*
PackageBuilder.java:290*)

at org.drools.compiler.PackageBuilder.addKnowledgeResource(*
PackageBuilder.java:488*)

at org.drools.builder.impl.KnowledgeBuilderImpl.add(*
KnowledgeBuilderImpl.java:25*)

at com.myfirm.RuleFlowProcess.init(*RuleFlowProcess.java:25*)

at com.myfirm.RuleFlowProcess.clone(*RuleFlowProcess.java:53*)

at com.myfirm.ThreadTest$CloneThread.run(*ThreadTest.java:35*)

Caused by: *java.lang.NullPointerException*

at org.mvel2.MVELInterpretedRuntime.parseAndExecuteInterpreted(*
MVELInterpretedRuntime.java:113*)

... 27 more



The exception normally occurs at KnowledgeBuilder.add(...) method. This can happen infrequently depending on the thread behavior. But there is always a chance to find this error in your production environment.
Now the question is, how do we resolve this? One way, is to share the same knowledge base for all threads. But due to synchronized APIs you will not achieve the performance. Threads will run sequentially. There is another simple solution -




public void buildKnowledgeBase()
{
    //MyClass is holding this method
    synchronized(MyClass.class)
    {
       KnowledgeBuilder kbuilder = 
                KnowledgeBuilderFactory.newKnowledgeBuilder();
       kbuilder.add( ResourceFactory.newUrlResource( "file://myrules.drl" ),
                ResourceType.DRL);
       assertFalse( kbuilder.hasErrors() );
       KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();
}

I have used synchronization on current Class object (don't confuse with the new instance of class). There is a single lock available for a Class. So, we can make threads sequential by synchronizing on the class level lock. This will not hit the performance much. Once the KnowledgeBase is created you can run parallel threads for rule execution. No further synchronization required.