Replace KieContainer at runtime on changes in Drools .drl files

Environment

  • Drools version: 7.6.0 Final
  • Java 8

Goal

My application is a server program that contains functionality for executing Drools rules. The Drools definitions reside in .drl files within a configured folder. At program start, I create a KieContainer with a few lines of Java code. I then use a folder monitor to watch for file changes, and in that case I create a new secondary KieContainer. If that fails (checked by kieBuilder.getResults().hasMessages(Message.Level.ERROR)) then an error is logged and the rules person can fix it. Else the live KieContainer is replaced with the new one.

I have a working version, but there seem to be side effects.

It is not my intention to use Maven and the KieScanner to re-deploy a new version. I use Maven, but I really don't think that's the right approach for my case.

Neither is an Execution Server with REST API

My solution should be simple. Compare to Logback, it can even reload the logback.xml config file by itself behind the scenes. My feeling is that this is more complicated than necessary.

Code

The create() method is used at program start, and then again when a change is seen by the file system:

private KieContainer create() {
    KieServices kieServices = KieServices.Factory.get();
    KieFileSystem kieFileSystem = kieServices.newKieFileSystem();

    File[] rulesFiles = rulesDir.listFiles((dir, name) -> name.endsWith(".drl"));
    for (File rulesFile : rulesFiles) {
        final Resource resource = ResourceFactory.newFileResource(rulesFile.getAbsolutePath());
        String targetPath = rulesFile.getAbsolutePath().replaceAll("\\\\", "\\/"); //slashes must be standardized for it to work
        resource.setTargetPath(targetPath);
        kieFileSystem.write(resource);
    }

    KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem);
    kieBuilder.buildAll();
    Results results = kieBuilder.getResults();
    if (results.hasMessages(Message.Level.ERROR)) {
        //logging not shown for brevity
        //PROBLEM: even though we abort here, it seems that the lines above modify the kieContainer that is currently in use!
        throw new IllegalStateException("Errors occurred while reading rules.");
    }

    KieModule kieModule = kieBuilder.getKieModule();
    return kieServices.newKieContainer(kieModule.getReleaseId());
}

When there is a problem in a Drools file, the create aborts, and the current KieContainer is kept.

If however it succeeds, the code to replace looks like:

/**
 * Called from the directory watcher, so we are in a separate thread here.
 */
private void refresh() {
    try {
        KieContainer oldContainer;

        //first try to create a new one behind the scenes, while requests concurrently still use the old one:
        KieContainer newContainer = create();
        //then swap it out, thread safe:
        synchronized (this) {
            oldContainer = this.kieContainer;
            this.kieContainer = newContainer;
        }
        //log not shown for brevity

        //then we have time to dispose the old one while no one waits:
        if (oldContainer != null) {
            //the current container may still be used by current threads.
            //i am not aware what dispose does in this regard, i think we should wait a bit until all use is finished.
            Thread.sleep(2000);
            oldContainer.dispose();
        }

    } catch (Exception e) {
        //log not shown for brevity
    }
}

The problem

A few times when someone edited .drl files and there were errors strange things happened. The KieContainer was not replaced (as desired), but side effects from the Drools method calls seem to happen.

KieServices is a singleton, fine. We create a new KieFileSystem, looks good, new disconnected instance. We call kieFileSystem.write(), should be no problem? Then kieServices.newKieBuilder() again returns a new disconnected instance. Therefore kieBuilder.buildAll() should be fine?

When debugging, I can see that both KieBuilder have the same KieModule instance?

One way how an erroneous and aborted refresh happens is when the Drools editor person copies a .drl file, intending to edit it afterwards. The file system picks up the change, and loading the rules fails because we have a duplicate file (same content, same package).

Question

What's wrong with my code, what can I change to make a new KieContainer creation completely disconnected from the previously loaded one?

droolskie

Answers

comments powered by Disqus