I am working on a Java application that uses the Drools rule engine to evaluate multiple DRL files, each containing different rules. Each rule is supposed to add a value to a global List if certain conditions are met.
I have two DRL files, Apple.drl and Mango.drl, both sharing a global Collection called myBasketList. Here are the rules defined in these files:
Apple.drl
global FruitUtility fruitUtility; global Collection myBasketList;
rule "AppleRule"
salience 10
when
$basket : Basket()
eval(fruitUtility.isApple($basket.fruit))
then
myBasketList.add("Apple");
end
Mango.drl
global FruitUtility fruitUtility; global Collection myBasketList;
rule "MangoRule"
salience 10
when
$basket : Basket()
eval(fruitUtility.isMango($basket.fruit))
then
myBasketList.add("Mango");
end
The code responsible for executing these rules creates a KieSession, sets the global variable, and fires the rules based on certain conditions. Here's how it looks:
@Override
public Collection<String> executeRules(RuleParameters ruleParameters) {
Collection<String> myBasketList = new ArrayList<String>(); final Map<String, Object> ruleGlobals = ImmutableMap.<String, Object>builder()
.put("fruitUtility", fruitUtility)
.put("myBasketList", myBasketList)
.build();
//some code to get the rulesProvider
droolsExecutor.executeRules(rulesProvider, ruleParameters.getFruit(), ruleGlobals);
return myBasketList;
}
This is the rulesProvider
public interface RulesProvider { List<Resource> getRules();
String getKieSessionName();
String getKieBaseName();
}
@Getter
@AllArgsConstructor
public class Basket
{
private String fruit;
}
public class FruitUtility{
public boolean isApple(String input){
if(input == "Apple"){
return true;
}
return false;
}
public boolean isMango(String input)
{
if(input == "Mango")
{
return true;
}
return false;
}
}
I am creating a closeableKieSession and setting the global variable.
public void executeRules(@NonNull final RulesProvider rulesProvider, @NonNull final Object fact, Map<String, Object> globals) throws RuleEvaluationException {
final KieContainer kieContainer = droolsKieContainerProvider.getKieContainer(rulesProvider);
/**
* A new session creation is required each time, because KieSession.fireAllRules is not thread safe.
* Only a single thread enters fireAllRules at a time.
* If a new session is not created every time, the rules will become a blocker for processing multiple
* messages in parallel.
* KieContainer creation is a heavy operation, and this KieContainer is a singleton.
*/
final KieSession session = kieContainer.newKieSession();
try (CloseableKieSession closeableSession = new CloseableKieSession(session)) {
final KieSession kieSession = closeableSession.getSession();
if (globals != null) {
globals.forEach((key, val) -> {
if (val == null) {
throw new IllegalArgumentException("Null value passed for global variable: " + key);
}
kieSession.setGlobal(key, val);
});
}
setupFactAndFireAllRules(fact, kieSession);
} catch (DependentServiceFailureException exception) {
throw exception;
} catch (RuntimeException exception) {
throw new RuleEvaluationException(String.format(EXCEPTION_WHEN_EVAL_FACT, fact), exception);
}
}
The executeRules method uses a RulesProvider to obtain the DRL rules, and a KieSession is created and managed with a custom CloseableKieSession.
Now, here's the issue: In one instance over two months of running this code, the behaviour was inconsistent. The fruit string passed to the rule was "Apple". Instead of "Apple" being inserted into myBasketList (as expected), "Mango" was inserted. Furthermore, it's worth noting that the "MangoRule" was executed before the "AppleRule".
I suspect that there might be a problem with the thread safety of the KieSession, but I cannot reproduce this issue, as in my testing, only "Apple" is inserted into the list as expected.
Does anyone have insights into why this inconsistency might occur? Are there any potential thread safety issues with the way KieSession is being used in this scenario? Any guidance or debugging tips would be greatly appreciated.