About Drools and Infinite Execution Loops - Esteban's Blog

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 16

Menu

Esteban's Blog
/dev/rnd

About Drools and Infinite Execution Loops


Infinite recursion is a typical problem when we are working with Drools (and possibly any other rule
engine). The inference mechanism going on inside Drools requires rules to be re-evaluated and
executed as needed. This powerful feature about rule engines could be really beneficial some times,
but some other times it could be a really PITA. I’ll try to analyse in this post the origins of this
recursion and the most common ways we have to deal with them in Drools: control fields, control
facts, rule attributes and Fine grained property change listeners.

When we are authoring rules we must take extra care for 2 different situations that could lead to
infinite recursion:

1. Fact modifications from within a rule could create a new activation of the same rule (self-loop).
2. Fact modifications from within a rule could activate a different rule that will eventually re-activate
the original rule (complex-loop).

Let’s analyse how we can deal with these 2 types of recursion in Drools.

Self-loops

Dealing with the first type of recursion (self-loop) most of the times is trivial. We just need to make
sure that the constraints present in the rule are exhaustive enough to avoid re-activations caused by
the right hand side (RHS) of the rule. So, for example if we have a rule saying that Customers with
more than 3 years seniority have a 10% discount we could start with a rule of the type:

rule "Apply more than 3 years seniority discount" //Case1.java


when
$c: Customer(seniority > 3)
then
modify($c){
setDiscount($c.getDiscount()+0.1),
addDiscountReason("Seniority grater than 3 years")
}
end

Of course this is a very basic rule, but it has some serious problem though. The problem is that the
modification of the Customer fact in the RHS will generate a new activation and execution of the
same rule. This is a very common problem most of us faced when started with Drools.

The first solution (and maybe the most intuitive one) is to avoid the use of modify() in the RHS of the
rule. Without the modify() call, Drools will never know about the change in Customer fact and the
rule is not going to be re-evaluated and re-activated again. Unfortunately, this magical solution has
its own disadvantages and sometimes we can’t just go for it. Let’s suppose for example that we
have another rules saying that no Customer (no matter his seniority) could have a discount grater
than 25%. This rule may look like this:

rule "No Customer has a discount grater than 25"


when
$c: Customer(discount > 0.25)
then
modify($c){
setDiscount(0.25)
}
end

If we were not using a modify() in the rules that apply discounts, latter rule will never be activated. In
these type of situations, letting Drools know about the modification in certain facts is desired and
even required.

Now that we have described the problem, let’s analyse 3 different ways to deal with self-loop
problems in Drools: control fields, control facts and the usage no-loop attribute

Control fields: Narrow down Rule’s Constraints to avoid re-activation

Sometimes, the easiest solution is to narrow down the constraints of the rule to exclude the already
modified facts. In the case of “Apply more than 3 years seniority discount” rule we could use the list
of discount reasons of Customer fact to check whether the current discount was already applied or
not:

rule "Apply more than 3 years seniority discount" //Case2.java


when
$c: Customer(seniority > 3, discountReasons not contains "Seniority
then
modify($c){
setDiscount($c.getDiscount()+0.1),
addDiscountReason("Seniority grater than 3 years")
}
end

The downside of this elegant solution is that we have to analyse what we are modifying in the RHS
of each rule to check if the conditions of the rule are prepared to deal with them or not. A change in
the RHS could lead to changes in the Conditions of the rule. Maintaining these kind of rules is most
of the times a complex task. If your rules are not going to change over time or if the change rate is
not elevated you can evaluate whether this is a good solution for you or not.

Adver tisement
Intimacy Matters

Discover the secrets of connection in


relationships.

A common technique commonly used when using control fields is to create rule-specific fields in
the fact classes instead of using business-related fields.

Control Facts: Narrow down Rule’s Constraints without polluting your Facts

Using control fields is a good approach but as we saw it has some flaws. Sometimes we don’t have
any good candidate among the fields of our fact classes to be used as a control field. In that case,
as we saw earlier, the creation of a specific field in our fact classes could be a solution. But even
with a specific control field things could get complex. The first obvious problem is that we are
polluting our facts with fields that are not business-specific. Even if we are ok with that, using
control fields gets more complicated with rules involving more than 1 fact. In those cases, we
usually require 1 control field for each involved fact and, even worst, the value of these fields must
be related to the combination of facts.

A good way to overcome some of the limitations control fields have is to use control facts instead.
A control fact instance will represent the execution of a specific rule for a specific fact set. Instead
of modifying the structure of our fact classes or add specific constraints to each of the patterns in
our rules we could use a single fact instance for this:
rule "Apply more than 3 years seniority discount" //Case3.java
when
$c: Customer(seniority > 3)
not ThreeYearsSeniorityDiscountApplied(customer == $c)
then
modify($c){
setDiscount($c.getDiscount()+0.1),
addDiscountReason("Seniority grater than 3 years")
}

insert (new ThreeYearsSeniorityDiscountApplied($c));


end

In the latter rule we can verify how the Customer pattern is clean and simple again. We have moved
the re-activation prevention part into a specific fact class called
ThreeYearsSeniorityDiscountApplied. The RHS of the rule is in charge of the insertion of one of this
facts after a customer was processed. In this case, changes in the RHS don’t require a change in the
LHS of the rule since the lock is not implemented in the Customer pattern but in our special fact.

Since there is not such a thing as a perfect solution, this approach has its own drawbacks too. If we
follow the recipe to the letter, we will need to create a specific control fact for each of the rules we
have. An improvement over this solution could be to use a generic control fact with some field
specifying the rule for which it applies:

rule "Apply more than 3 years seniority discount"


when
$c: Customer(seniority > 3)
not DiscountApplied(type == "3 years", customer == $c)
then
modify($c){
setDiscount($c.getDiscount()+0.1),
addDiscountReason("Seniority grater than 3 years")
}
insert (new DiscountApplied("3 years", $c));
end

But, even if this solution is more flexible and maintainable than having 1 control fact for each rule, it
has the problem that the RHS and LHS depends (in a low degree) on each other; a typo in one of the
“3 years” strings will create an infinite loop during runtime.

no-loop attribute

Since self-loops are something very common to find in our rules, Drools already comes with a
special attribute we can use in our rules to avoid these situations: no-loop.

A rule marked with the no-loop attribute will not be re-activated as a direct consequence of the
execution of its RHS. So, no matter what we do in the RHS of the rule, it is never going to be re-
activated again:

rule "Apply more than 3 years seniority discount" //Case4.java


no-loop true
when
$c: Customer(seniority > 3)
then
modify($c){
setDiscount($c.getDiscount()+0.1),
addDiscountReason("Seniority grater than 3 years")
}
end

Too good to be true, right? No unnecessary fields in our facts, no unnecessary fact classes nor
conditions in our rules. The RHS is concise and only deals with business logic. For self-loop
problems, the use of no-loop is indeed an elegant solution. So, if we have isolated rules creating
infinite self-loops we should consider to use this attribute. Of course we should never abuse of this
(or any) attribute; each time we use an attribute in a rule, we are moving one step away from the
declarative approach Drools represents.
As an introduction for complex-loops, let’s analyse in the following section the most important
limitation of no-loop: it serves for individual rules only.

Complex-loops

Let’s say we have now another rule in our knowledge base stating “Customers with more than 5
years seniority have an extra 10% discount”. So, a customer with 6 years seniority will have a 20%
total discount:

rule "Apply more than 3 years seniority discount" //Case5.java


no-loop true
when
$c: Customer(seniority > 3)
then
modify($c){
setDiscount($c.getDiscount()+0.1),
addDiscountReason("Seniority grater than 3 years")
}
end

rule "Apply more than 5 years seniority discount"


no-loop true
when
$c: Customer(seniority > 5)
then
modify($c){
setDiscount($c.getDiscount()+0.1),
addDiscountReason("Seniority grater than 5 years")
}
end

In the latter case, the use of no-loop is not enough. Let’s analyse what happens when “Apply more
than 3 years seniority discount” rule is executed. The execution of this rule modifies the customer
object and lets Drools to know about it. This action would generate 2 new activations: a new
activation for “Apply more than 3 years seniority discount” (discarded because of the use of no-loop)
and an activation of “Apply more than 5 years seniority discount” rule. So far so good; we have
avoided the first infinite loop. The problem is when the activation of “Apply more than 5 years
seniority discount” is executed. Since this rule is also modifying the customer fact, 2 new
activations should happen: a new activation of itself (avoided thanks to the no-loop attribute) and an
activation of “Apply more than 3 years seniority discount” again. And here is where the infinite loop
appears. The loop is not related to a single rule anymore; in this case is the presence of the 2
discount rules what generates the loop. In more complex situation is common to observe these kind
of complex-loops involving several layers of rules.

So let’s see what are the options we have to mitigate this issue.

Control Fields and Control Facts

Just like with self-loops, the use of control fields and control facts is a valid solution for this
situation. A version of the last 2 rules using control facts could look like this:

rule "Apply more than 3 years seniority discount" //Case6.java


when
$c: Customer(seniority > 3)
not DiscountApplied(type == "3 years", customer == $c)
then
modify($c){
setDiscount($c.getDiscount()+0.1),
addDiscountReason("Seniority grater than 3 years")
}

insert( new DiscountApplied("3 years", $c) );


end

rule "Apply more than 5 years seniority discount"


when
$c: Customer(seniority > 5)
not DiscountApplied(type == "5 years", customer == $c)
then
modify($c){
setDiscount($c.getDiscount()+0.1),
addDiscountReason("Seniority grater than 5 years")
}

insert( new DiscountApplied("5 years", $c) );


end

The same benefits and disadvantages we found for control fields and facts in self-loops apply here.

lock-on-active attribute

For some situations where complex-loops appear, Drools comes with a handy attribute we may use:
lock-on-active. This attribute, that is described as a “Stronger version of no-loop” in Drools’
documentation, could be used to avoid infinite execution loops involving one or more rules. But we
need to understand its behavior so we can know whether we can use it or not in a specific situation.

What lock-on-active basically does is to prevent a rule to be activated if the agenda group where the
rule is defined is already active. So, let’s see how a version of our discount rules looks like when
using lock-on-active attribute. In this case we are also going to be adding a new discount rule for the
following case: “Customers with more than 3 children receive an extra 10% discount”:

rule "Apply more than 3 years seniority discount" //Case7.java


lock-on-active true
when
$c: Customer(seniority > 3)
then
modify($c){
setDiscount($c.getDiscount()+0.1),
addDiscountReason("Seniority grater than 3 years")
}
end

rule "Apply more than 5 years seniority discount"


lock-on-active true
when
$c: Customer(seniority > 5)
then
modify($c){
setDiscount($c.getDiscount()+0.1),
addDiscountReason("Seniority grater than 5 years")
}
end

rule "Apply more than 3 children discount"


lock-on-active true
when
$c: Customer(childrenCount > 3)
then
modify($c){
setDiscount($c.getDiscount()+0.1),
addDiscountReason("More than 3 children")
}
end

In this case, everything works as expected; after the execution of these 3 rules, a customer with
more than 5 years seniority and more than 3 children will receive a discount of 30%. Every time the
customer is modified by one of the rules, no new activation is created because all of the rules are
defined in the same agenda group (MAIN) and at the time the modification occurs the group is
already active. Just like with no-loop, things seem to resolve in a magical way once again: no
bloating fields nor facts, rules that only “talk” about business. Fantastic! Isn’t it?

But what about the rule that prevented a customer to have a discount grater than 25%? Do we need
to use lock-on-active in that rule too? After all, the RHS of the rule is modifying the customer and
could lead to infinite complex-loops too. Let’s see what happens if we add the lock-on-active
attribute to it:

rule "No Customer has a discount grater than 25" //Case8.java


lock-on-active true
when
$c: Customer(discount > 0.25)
then
modify($c){
setDiscount(0.25)
}
end

Contrary to what we would’ve expected, using lock-on-active in this situation doesn’t generate the
expected result. If we insert a customer with more than 5 years of seniority and more than 3
children and we fire all the rules, the final discount is still 30%. How is that possible? The important
thing to understand here is when the rules are evaluated and when an agenda group becomes
active. After we insert the mentioned customer in the session, all the rules get evaluated; the result
are 3 activations (The latter rule is not being activated since the current discount of the customer is
0%). Once we call fireAllRules(), the agenda group having the focus (by default ‘MAIN’ agenda group)
will be activated and one of its activations is going to be selected and executed. After this point no
new activations could happen for rules belonging to the active agenda group and having lock-on-
active attribute defined. That is why after the third discount rule is executed, the 25% limit rule is not
going to be activated even if the current state of the objects in the session match its constraints.

For this particular situation, a simple solution could be to remove the lock-on-active attribute from
the 25% limit rule. This will allow its activation after the third discount rule is executed. For more
complex situations, things may not be so easy. The important point here is to understand the
behavior of lock-on-active to see whether it is a good candidate to be used by us in a specific
situation or not.

Property Reactive Facts

Let’s go back for a minute to our original rule and let’s analyse from a different perspective what is
“wrong” with it (from a rule engine point of view of course):

rule "Apply more than 3 years seniority discount" //Case9.java


when
$c: Customer(seniority > 3)
then
modify($c){
setDiscount($c.getDiscount()+0.1),
addDiscountReason("Seniority grater than 3 years")
}
end

The “problem” with this rule is that when we modify a fact object, Drools doesn’t know what we have
modified in it, and thus re-evaluates all the rules associated with it to find new matches. The infinite
loop happens because among the re-evaluated rules we find the same rule that have modified the
fact. This behavior is as explained because, since its beginnings, Drools has treated the RHS of a
rule as a black box. Fortunately for us, things have changed since version 5.4 and now we have a
feature called “Property Reactive” or “Fine grained property change listener” that allows Drools to
understand a little bit more about what is going on inside the RHS of our rules, or at least inside the
modify() operation.

The idea behind “Property Reactive” facts is that rules should only react upon “interesting” changes
in the facts they are matching and not upon every single modification. One of the ways we have to
enable this feature is to mark our fact classes with Drools’ @PropertyReactive annotation. When a
fact marked with this annotation is found in a rule, the rules automatically adds a “property change
listener” to the attributes of this class being used in the rule. The rule will then react only on
changes affecting those attributes:

@PropertyReactive
public class Customer {
...
}

In the case of our initial rule, a property change listener is going to be automatically configured for
Customer.seniority field. Any modification on the fact that doesn’t affect its seniority field is not
going to cause a re-evaluation of this pattern avoiding this way a possible re-activation of the rule.
Property change listeners can be explicitly defined and overwritten in the same rule using the
@Watch annotation:

when
$c: Customer(seniority > 3) @Watch(childrenCount)
then

In this case, the pattern will react upon modifications in seniority (automatic) and childrenCount
changes. Using wildcards we can listen to any (@Watch(*)) or no change at all (@Watch(!*)).

By using @PropertyReactive annotation in our Customer fact class we don’t need to do anything
special in our discount rules: no need for custom fields nor facts and no need for special rule
attributes neither. In the case of the 25% limit rule, the use of property specific doesn’t bring any
advantage. The particularity of that rule is that it modifies the same field that it is listening to:
discount. The self-loop here is prevented by the fact that the condition of the rule and the changes
performed to the customer fact in its RHS are mutually exclusive. Setting the discount of the
customer to 25 will immediately avoid a re-activation of the same rule: it is only interested in
customers with a discount GRATER than 25.

Things would have been different if the condition of the rule would have been “seniority >= 0.25”. In
this case, even with property reactive facts, we are going to experience an infinite execution self-
loop here. One possible solution for this hypothetical situation could be the use of the no-loop
attribute.

Conclusion

We have covered the reasons and types of infinite execution loops we could find in Drools.
According to the nature of the loop; self-loops or complex-loops, we have analysed the different
ways we have to avoid them. As always, the conclusion is that there is no silver bullet here. The best
way to deal with this issue is to know what Drools provides to mitigate it and to analyse the best
solution for our particular situation. Most of the times an homogeneous solution is not possible and
we have to appeal to a mixed solution.

The code used in this post could be found in my github account: https://github.com/esteban-
aliverti/DroolsRecursionPost

November 16, 2012  9 Replies

« Previous Next »
Leave a comment

Julius on July 15, 2013 at 8:31 pm

Brilliant article.

 Reply

manish on February 21, 2014 at 8:01 pm

good article… it helps

 Reply

Pingback: ¿Cómo evitar bucles infinitos en Drools? | Antonio Mendoza Pérez

Toral on September 6, 2014 at 5:49 pm

Excellent article and great explanation

 Reply

Ebrima on July 1, 2015 at 3:23 am

Great

 Reply

Torsten on September 11, 2015 at 4:05 pm

just perfect!

 Reply

Hasan Tayyar BESIK on November 24, 2015 at 9:01 pm

Thank you. just made my day.


 Reply

Benson Hoi on May 25, 2016 at 12:52 pm

This is the best tHe best blog article related Drools that I have read this year 🙂
 Reply

Kiran Gurude on September 14, 2017 at 12:02 pm

Best blog I came across about Drools. 🙂


 Reply

View Esteban Aliverti's profile on Masterbranch


Endorse esteban-aliverti on Coderwall

Recent Posts

Drools 6 in JBug Denmark


Mastering JBoss Drools 6 is out
Drools and JBPM6 Workshops and Training Courses (21/25 October @London)
jBPM5 Developer Guide published!
jBPM5 Developer Guide presentation in Barcelona (12/19/2012)

Twitter
Tweets from @ilesteban Follow

Esteban Aliverti reposted

Arduino @arduino · Oct 12, 2019


This autonomous portrait painter turns G-code into artwork: bit.ly/33rFPjQ

hackaday and David Cuartielles

1 44

Esteban Aliverti reposted

Cognitive Medical @CogMedSys · Aug 8 2018

View Full Site

Create a free website or blog at WordPress.com.

You might also like