Home > database >  Java Drools - REST Call Timing Out - Out Of Memory
Java Drools - REST Call Timing Out - Out Of Memory

Time:10-28

I am implementing Drools in a Java Project, version 6 of the below dependencies:

<dependency>
    <groupId>org.drools</groupId>
    <artifactId>drools-core</artifactId>
    <version>6.0.1.Final</version>
</dependency>

<dependency>
    <groupId>org.drools</groupId>
    <artifactId>drools-compiler</artifactId>
    <version>6.0.1.Final</version>
</dependency>

I intent to invoke the rules by calling a REST API, built with spring 4.3.0.RELEASE. In my config class, I am loading the drools script from database:

@Configuration
@EnableWebMvc
@EnableAsync
@ComponentScan(basePackages = "com.bla.bla.api.app")
@ImportResource({ "classpath:datasources-context.xml" })
public class AppConfig {
 private static final Log log = LogFactory.getLog(AppConfig.class);  
 public @Autowired IServiceDao serviceDao;

The rules are then being stored in a global variable as list in a @Repository annotated class:

@Repository
public class ServiceDao implements IServiceDao

In the above ServiceDao there is a method, invoked at the post construct stage:

@PostConstruct
    private void init() throws IOException {
        log.debug("Initializing ruleevaluation API Context");

        // Initializing rules:
        serviceDao.initializeRules();
    }

, that reloads the rules from database and initializes the drools environment:

public void initializeRules() {
        

        // 1. Load Rules:
        log.debug("Initiating Rules...");
        rules.clear();
        rules = loadRules();

        //  2. Initialize Drools Environment:
        try {

            initializeDrools();

        } catch (DroolsParserException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        
    }

the method 'initializeDrools()' is implemented as below:

private void initializeDrools() throws DroolsParserException, IOException {

        PackageBuilder packageBuilder = new PackageBuilder();
        RuleBase ruleBase = RuleBaseFactory.newRuleBase();
        
        // This script will contain all the rules combined in one Drools script:
        String completeRulesDroolsScript = "";
        // Initialize script with object imports:
        completeRulesDroolsScript = ""
                  "import com.bla.model.svc.RuleRequest             \r\n"
                  "import com.bla.model.svc.RuleResponse        \r\n\n\n";

        if (rules != null) {

            if (rules.size() > 0) {
                for (RuleObject ruleObject : rules) {

                    // Add each individual Drools rule expression to the complete Drools script:
                    completeRulesDroolsScript  = ruleObject.getExpression();
                    completeRulesDroolsScript = completeRulesDroolsScript .concat("\n\n\n");
                }
            }

        }

        try {
            packageBuilder.addPackageFromDrl(new 
            StringReader(completeRulesDroolsScript));
            org.drools.core.rule.Package rulesPackage = packageBuilder.getPackage();            
            ruleBase.addPackage(rulesPackage);
            
            this.setWorkingMemory(ruleBase.newStatefulSession());
        }
        catch(Exception e) {
            log.error("!!! Could not Initialize Drools Engine !!!");
        }
        
    }

Next is the REST method, in a separate controller class:

@RestController
public class RuleEvaluationController

which simply calls the backend service from ServiceDao class:

    @PostMapping(value = "/evaluateRule")
    public ResponseEntity<RuleEvaluationResponse> evaluateRuleData(
            @RequestBody RuleEvaluationRequest ruleEvaluationRequest) {     

        RuleEvaluationResponse ruleEvaluationResponse = new RuleEvaluationResponse();
        try {

            // 1. get the parameters list:
            log.debug(ruleEvaluationRequest.toString());
            
            RuleEvaluationResponse response = serviceDao
                    .evaluateRule(ruleEvaluationRequest);

            log.debug("response = "   response.getEntry());

            return new ResponseEntity<RuleEvaluationResponse>(response,
                    HttpStatus.OK);

        } catch (Exception e) {

            log.error("!!!!! Exception Occurred:"   e.getMessage());

            // set request id, and date in millis:
            ruleevaluationResponse.setRequestId(UUID.randomUUID().toString());
            ruleevaluationResponse.setDate(System.currentTimeMillis());

            ruleevaluationResponse.setErrorCode(Constants.FAILURE);
            ruleevaluationResponse.setErrorDescription(e.getMessage());

            return new ResponseEntity<RuleEvaluationResponse>(ruleevaluationResponse,
                    HttpStatus.OK);
        }
    }

Where serviceDao.evaluateRule simply inserts the request and response objects, and then fires the rules:

public RuleEvaluationResponse evaluateRule(RuleEvaluationRequest request) {

        RuleEvaluationResponse response = new RuleEvaluationResponse();

        this.getWorkingMemory().insert(response);
        this.getWorkingMemory().insert(request);
        this.getWorkingMemory().fireAllRules();

        log.debug("Response = "   response.getEntry());

        return response;
    }

At first this all works, and I am getting the rules evaluated successfully and the expected response is obtained. However after 4 or 5 REST calls, the next rest call takes forever and never returns a response and after a while I get an out of memory error.

Can anyone tell me what am I missing here? any hints and advice are highly appreciated. Please feel free to ask for more details if the above info is insufficient.

Thanks

CodePudding user response:

You didn't provide all code so maybe you have cleanup somewhere else but it looks like you are inserting and calling .fireAllRules() and not cleaning up. My code works with KieSession and looks more or less like this:

kSession = container.newKieSession("name");
kSession.insert(...);
kSession.insert(...);
kSession.insert(...);

kSession.fireAllRules();
Object[] objects = kSession.getObjects();
kSession.dispose();

You may have to change your code. https://docs.jboss.org/drools/release/6.0.1.Final/drools-docs/html_single/#d0e3555 The statefull session described in the doc requires deleting objects after first fireAllRules() call. So you either create stateless session and dispose of it every time or use stateful session and delete old objects before inserting new.

I would probably go with stateless (because I am doing it already ;)

[...]
try {
    packageBuilder.addPackageFromDrl(new 
        StringReader(completeRulesDroolsScript));
    org.drools.core.rule.Package rulesPackage = 
        packageBuilder.getPackage();            
    ruleBase.addPackage(rulesPackage);
        
    this.setWorkingMemory(ruleBase.newStatefulSession());
}
[...]

I am assuming all the heavy lifting (parsing DRL, etc.) is happening before newStatefulSession() call and creating a session is not 'expensive'.

CodePudding user response:

Thank you for the hints and advice provided, they were helpful in finding the solution.

I solved the problem as follows:

First I migrated to new syntax of version 6.., using KieSession.

first I initialized the Kie Components as below:

// Define Kie Service from the Drools factory:
KieServices ks = KieServices.Factory.get();

// Define Drools Kie Repository to house the rules:
KieRepository kr = ks.getRepository();

// By default, a Kie File System is defined. In this case rules are NOT being
// read from the *.drl file, but instead fetched from Database.
KieFileSystem kfs = ks.newKieFileSystem();

// Write rules into the Kie file system. Specified file is irrelevant and can be
// empty, since we are not reading from *.drl file.
kfs.write("src/main/resources/com/rule/tempRules.drl", completeAccountingRulesDroolsScript);

// Define rule builder of the rules in file system object:
KieBuilder kb = ks.newKieBuilder(kfs);

// Build all rules in the script.kieModule is automatically deployed to
// KieRepository if successfully built.
kb.buildAll();

// Define new session container. This container is global and will be available
// for all subsequent rule invocations:
kContainer = ks.newKieContainer(kr.getDefaultReleaseId());

In my REST call I replaced the code with the following:

kSession = kContainer.newKieSession();
kSession.insert(response);
kSession.insert(request);
kSession.fireAllRules();

Collection<? extends Object> objects = kSession.getObjects();
kSession.dispose();

and now the variable kContainer is a global variable initialized only on startup. At the end of each REST call I am disposing of the session. Future calls will create a kie session for each call. Could use your input if this is best approach.

@user3075118 @ Roddy of the Frozen Peas

thank you for the hint, it was the one that solved the issue.

  • Related