Chapter 83. Execution control in the decision engine

When new rule data enters the working memory of the decision engine, rules may become fully matched and eligible for execution. A single working memory action can result in multiple eligible rule executions. When a rule is fully matched, the decision engine creates an activation instance, referencing the rule and the matched facts, and adds the activation onto the decision engine agenda. The agenda controls the execution order of these rule activations using a conflict resolution strategy.

After the first call of fireAllRules() in the Java application, the decision engine cycles repeatedly through two phases:

  • Agenda evaluation. In this phase, the decision engine selects all rules that can be executed. If no executable rules exist, the execution cycle ends. If an executable rule is found, the decision engine registers the activation in the agenda and then moves on to the working memory actions phase to perform rule consequence actions.
  • Working memory actions. In this phase, the decision engine performs the rule consequence actions (the then portion of each rule) for all activated rules previously registered in the agenda. After all the consequence actions are complete or the main Java application process calls fireAllRules() again, the decision engine returns to the agenda evaluation phase to reassess rules.

Figure 83.1. Two-phase execution process in the decision engine

Two Phase enterprise

When multiple rules exist on the agenda, the execution of one rule may cause another rule to be removed from the agenda. To avoid this, you can define how and when rules are executed in the decision engine. Some common methods for defining rule execution order are by using rule salience, agenda groups, or activation groups.

83.1. Salience for rules

Each rule has an integer salience attribute that determines the order of execution. Rules with a higher salience value are given higher priority when ordered in the activation queue. The default salience value for rules is zero, but the salience can be negative or positive.

For example, the following sample DRL rules are listed in the decision engine stack in the order shown:

rule "RuleA"
salience 95
when
    $fact : MyFact( field1 == true )
then
    System.out.println("Rule2 : " + $fact);
    update($fact);
end

rule "RuleB"
salience 100
when
   $fact : MyFact( field1 == false )
then
   System.out.println("Rule1 : " + $fact);
   $fact.setField1(true);
   update($fact);
end

The RuleB rule is listed second, but it has a higher salience value than the RuleA rule and is therefore executed first.

83.2. Agenda groups for rules

An agenda group is a set of rules bound together by the same agenda-group rule attribute. Agenda groups partition rules on the decision engine agenda. At any one time, only one group has a focus that gives that group of rules priority for execution before rules in other agenda groups. You determine the focus with a setFocus() call for the agenda group. You can also define rules with an auto-focus attribute so that the next time the rule is activated, the focus is automatically given to the entire agenda group to which the rule is assigned.

Each time the setFocus() call is made in a Java application, the decision engine adds the specified agenda group to the top of the rule stack. The default agenda group "MAIN" contains all rules that do not belong to a specified agenda group and is executed first in the stack unless another group has the focus.

For example, the following sample DRL rules belong to specified agenda groups and are listed in the decision engine stack in the order shown:

Sample DRL rules for banking application

rule "Increase balance for credits"
  agenda-group "calculation"
when
  ap : AccountPeriod()
  acc : Account( $accountNo : accountNo )
  CashFlow( type == CREDIT,
            accountNo == $accountNo,
            date >= ap.start && <= ap.end,
            $amount : amount )
then
  acc.balance  += $amount;
end

rule "Print balance for AccountPeriod"
  agenda-group "report"
when
  ap : AccountPeriod()
  acc : Account()
then
  System.out.println( acc.accountNo +
                      " : " + acc.balance );
end

For this example, the rules in the "report" agenda group must always be executed first and the rules in the "calculation" agenda group must always be executed second. Any remaining rules in other agenda groups can then be executed. Therefore, the "report" and "calculation" groups must receive the focus to be executed in that order, before other rules can be executed:

Set the focus for the order of agenda group execution

Agenda agenda = ksession.getAgenda();
agenda.getAgendaGroup( "report" ).setFocus();
agenda.getAgendaGroup( "calculation" ).setFocus();
ksession.fireAllRules();

You can also use the clear() method to cancel all the activations generated by the rules belonging to a given agenda group before each has had a chance to be executed:

Cancel all other rule activations

ksession.getAgenda().getAgendaGroup( "Group A" ).clear();

83.3. Activation groups for rules

An activation group is a set of rules bound together by the same activation-group rule attribute. In this group, only one rule can be executed. After conditions are met for a rule in that group to be executed, all other pending rule executions from that activation group are removed from the agenda.

For example, the following sample DRL rules belong to the specified activation group and are listed in the decision engine stack in the order shown:

Sample DRL rules for banking

rule "Print balance for AccountPeriod1"
  activation-group "report"
when
  ap : AccountPeriod1()
  acc : Account()
then
  System.out.println( acc.accountNo +
                      " : " + acc.balance );
end

rule "Print balance for AccountPeriod2"
  activation-group "report"
when
  ap : AccountPeriod2()
  acc : Account()
then
  System.out.println( acc.accountNo +
                      " : " + acc.balance );
end

For this example, if the first rule in the "report" activation group is executed, the second rule in the group and all other executable rules on the agenda are removed from the agenda.

83.4. Rule execution modes and thread safety in the decision engine

The decision engine supports the following rule execution modes that determine how and when the decision engine executes rules:

  • Passive mode: (Default) The decision engine evaluates rules when a user or an application explicitly calls fireAllRules(). Passive mode in the decision engine is best for applications that require direct control over rule evaluation and execution, or for complex event processing (CEP) applications that use the pseudo clock implementation in the decision engine.

    Example CEP application code with the decision engine in passive mode

    KieSessionConfiguration config = KieServices.Factory.get().newKieSessionConfiguration();
    config.setOption( ClockTypeOption.get("pseudo") );
    KieSession session = kbase.newKieSession( conf, null );
    SessionPseudoClock clock = session.getSessionClock();
    
    session.insert( tick1 );
    session.fireAllRules();
    
    clock.advanceTime(1, TimeUnit.SECONDS);
    session.insert( tick2 );
    session.fireAllRules();
    
    clock.advanceTime(1, TimeUnit.SECONDS);
    session.insert( tick3 );
    session.fireAllRules();
    
    session.dispose();

  • Active mode: If a user or application calls fireUntilHalt(), the decision engine starts in active mode and evaluates rules continually until the user or application explicitly calls halt(). Active mode in the decision engine is best for applications that delegate control of rule evaluation and execution to the decision engine, or for complex event processing (CEP) applications that use the real-time clock implementation in the decision engine. Active mode is also optimal for CEP applications that use active queries.

    Example CEP application code with the decision engine in active mode

    KieSessionConfiguration config = KieServices.Factory.get().newKieSessionConfiguration();
    config.setOption( ClockTypeOption.get("realtime") );
    KieSession session = kbase.newKieSession( conf, null );
    
    new Thread( new Runnable() {
      @Override
      public void run() {
          session.fireUntilHalt();
      }
    } ).start();
    
    session.insert( tick1 );
    
    ... Thread.sleep( 1000L ); ...
    
    session.insert( tick2 );
    
    ... Thread.sleep( 1000L ); ...
    
    session.insert( tick3 );
    
    session.halt();
    session.dispose();

    This example calls fireUntilHalt() from a dedicated execution thread to prevent the current thread from being blocked indefinitely while the decision engine continues evaluating rules. The dedicated thread also enables you to call halt() at a later stage in the application code.

Although you should avoid using both fireAllRules() and fireUntilHalt() calls, especially from different threads, the decision engine can handle such situations safely using thread-safety logic and an internal state machine. If a fireAllRules() call is in progress and you call fireUntilHalt(), the decision engine continues to run in passive mode until the fireAllRules() operation is complete and then starts in active mode in response to the fireUntilHalt() call. However, if the decision engine is running in active mode following a fireUntilHalt() call and you call fireAllRules(), the fireAllRules() call is ignored and the decision engine continues to run in active mode until you call halt().

For added thread safety in active mode, the decision engine supports a submit() method that you can use to group and perform operations on a KIE session in a thread-safe, atomic action:

Example application code with submit() method to perform atomic operations in active mode

KieSession session = ...;

new Thread( new Runnable() {
  @Override
  public void run() {
      session.fireUntilHalt();
  }
} ).start();

final FactHandle fh = session.insert( fact_a );

... Thread.sleep( 1000L ); ...

session.submit( new KieSession.AtomicAction() {
  @Override
  public void execute( KieSession kieSession ) {
    fact_a.setField("value");
    kieSession.update( fh, fact_a );
    kieSession.insert( fact_1 );
    kieSession.insert( fact_2 );
    kieSession.insert( fact_3 );
  }
} );

... Thread.sleep( 1000L ); ...

session.insert( fact_z );

session.halt();
session.dispose();

Thread safety and atomic operations are also helpful from a client-side perspective. For example, you might need to insert more than one fact at a given time, but require the decision engine to consider the insertions as an atomic operation and to wait until all the insertions are complete before evaluating the rules again.

83.5. Fact propagation modes in the decision engine

The decision engine supports the following fact propagation modes that determine how the decision engine progresses inserted facts through the engine network in preparation for rule execution:

  • Lazy: (Default) Facts are propagated in batch collections at rule execution, not in real time as the facts are individually inserted by a user or application. As a result, the order in which the facts are ultimately propagated through the decision engine may be different from the order in which the facts were individually inserted.
  • Immediate: Facts are propagated immediately in the order that they are inserted by a user or application.
  • Eager: Facts are propagated lazily (in batch collections), but before rule execution. The decision engine uses this propagation behavior for rules that have the no-loop or lock-on-active attribute.

By default, the Phreak rule algorithm in the decision engine uses lazy fact propagation for improved rule evaluation overall. However, in few cases, this lazy propagation behavior can alter the expected result of certain rule executions that may require immediate or eager propagation.

For example, the following rule uses a specified query with a ? prefix to invoke the query in pull-only or passive fashion:

Example rule with a passive query

query Q (Integer i)
    String( this == i.toString() )
end

rule "Rule"
  when
    $i : Integer()
    ?Q( $i; )
  then
    System.out.println( $i );
end

For this example, the rule should be executed only when a String that satisfies the query is inserted before the Integer, such as in the following example commands:

Example commands that should trigger the rule execution

KieSession ksession = ...
ksession.insert("1");
ksession.insert(1);
ksession.fireAllRules();

However, due to the default lazy propagation behavior in Phreak, the decision engine does not detect the insertion sequence of the two facts in this case, so this rule is executed regardless of String and Integer insertion order. For this example, immediate propagation is required for the expected rule evaluation.

To alter the decision engine propagation mode to achieve the expected rule evaluation in this case, you can add the @Propagation(<type>) tag to your rule and set <type> to LAZY, IMMEDIATE, or EAGER.

In the same example rule, the immediate propagation annotation enables the rule to be evaluated only when a String that satisfies the query is inserted before the Integer, as expected:

Example rule with a passive query and specified propagation mode

query Q (Integer i)
    String( this == i.toString() )
end

rule "Rule" @Propagation(IMMEDIATE)
  when
    $i : Integer()
    ?Q( $i; )
  then
    System.out.println( $i );
end

83.6. Agenda evaluation filters

The decision engine supports an AgendaFilter object in the filter interface that you can use to allow or deny the evaluation of specified rules during agenda evaluation. You can specify an agenda filter as part of a fireAllRules() call.

The following example code permits only rules ending with the string "Test" to be evaluated and executed. All other rules are filtered out of the decision engine agenda.

Example agenda filter definition

ksession.fireAllRules( new RuleNameEndsWithAgendaFilter( "Test" ) );