/* * Copyright 2016 Red Hat, Inc. and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.drools.compiler.integrationtests; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import org.drools.compiler.util.debug.DebugList; import org.drools.core.ClockType; import org.drools.core.base.ClassObjectType; import org.drools.core.common.InternalWorkingMemory; import org.drools.core.impl.InternalKnowledgeBase; import org.drools.core.impl.StatefulKnowledgeSessionImpl; import org.drools.core.reteoo.CompositePartitionAwareObjectSinkAdapter; import org.drools.core.reteoo.EntryPointNode; import org.drools.core.reteoo.ObjectTypeNode; import org.drools.core.rule.EntryPointId; import org.drools.core.time.impl.PseudoClockScheduler; import org.junit.Test; import org.kie.api.KieBase; import org.kie.api.conf.EventProcessingOption; import org.kie.api.io.ResourceType; import org.kie.api.runtime.KieSession; import org.kie.api.runtime.KieSessionConfiguration; import org.kie.api.runtime.conf.ClockTypeOption; import org.kie.api.runtime.rule.FactHandle; import org.kie.internal.KnowledgeBaseFactory; import org.kie.internal.conf.MultithreadEvaluationOption; import org.kie.internal.utils.KieHelper; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.*; public class ParallelEvaluationTest { @Test(timeout = 10000L) public void test() { StringBuilder sb = new StringBuilder( 400 ); sb.append( "global java.util.List list;\n" ); for (int i = 0; i < 10; i++) { sb.append( getRule( i, "" ) ); } KieBase kbase = new KieHelper().addContent( sb.toString(), ResourceType.DRL ) .build( MultithreadEvaluationOption.YES ); EntryPointNode epn = ((InternalKnowledgeBase) kbase).getRete().getEntryPointNode( EntryPointId.DEFAULT ); ObjectTypeNode otn = epn.getObjectTypeNodes().get( new ClassObjectType( Integer.class ) ); assertTrue( ( (CompositePartitionAwareObjectSinkAdapter) otn.getObjectSinkPropagator() ).isHashed() ); KieSession ksession = kbase.newKieSession(); assertTrue( ( (InternalWorkingMemory) ksession ).getAgenda().isParallelAgenda() ); List<Integer> list = new DebugList<Integer>(); ksession.setGlobal( "list", list ); for (int i = 0; i < 10; i++) { ksession.insert( i ); ksession.insert( "" + i ); } ksession.fireAllRules(); assertEquals(10, list.size()); } @Test(timeout = 10000L) public void testWithInsertions() { StringBuilder sb = new StringBuilder( 4000 ); sb.append( "global java.util.List list;\n" ); int ruleNr = 200; for (int i = 0; i < ruleNr; i++) { sb.append( getRule( i, "insert( $i + 10 );\ninsert( \"\" + ($i + 10) );\n" ) ); } KieSession ksession = new KieHelper().addContent( sb.toString(), ResourceType.DRL ) .build( MultithreadEvaluationOption.YES ) .newKieSession(); assertTrue( ( (InternalWorkingMemory) ksession ).getAgenda().isParallelAgenda() ); List<Integer> list = new DebugList<Integer>(); ksession.setGlobal( "list", list ); for (int i = 0; i < 10; i++) { ksession.insert( i ); ksession.insert( "" + i ); } ksession.fireAllRules(); assertEquals(ruleNr, list.size()); } @Test(timeout = 10000L) public void testWithDeletes() { StringBuilder sb = new StringBuilder( 400 ); sb.append( "global java.util.List list;\n" ); for (int i = 1; i < 11; i++) { sb.append( getRule( i, "delete( $i );\n" ) ); } for (int i = 1; i < 11; i++) { sb.append( getNotRule( i ) ); } KieSession ksession = new KieHelper().addContent( sb.toString(), ResourceType.DRL ) .build( MultithreadEvaluationOption.YES ) .newKieSession(); assertTrue( ( (InternalWorkingMemory) ksession ).getAgenda().isParallelAgenda() ); List<Integer> list = new DebugList<Integer>(); ksession.setGlobal( "list", list ); for (int i = 1; i < 11; i++) { ksession.insert( i ); ksession.insert( "" + i ); } ksession.fireAllRules(); assertEquals(20, list.size()); } @Test(timeout = 10000L) public void testWithAsyncInsertions() { StringBuilder sb = new StringBuilder( 4000 ); sb.append( "global java.util.List list;\n" ); int ruleNr = 200; for (int i = 0; i < ruleNr; i++) { sb.append( getRule( i, "insertAsync( $i + 10 );\ninsertAsync( \"\" + ($i + 10) );\n" ) ); } KieSession ksession = new KieHelper().addContent( sb.toString(), ResourceType.DRL ) .build( MultithreadEvaluationOption.YES ) .newKieSession(); assertTrue( ( (InternalWorkingMemory) ksession ).getAgenda().isParallelAgenda() ); StatefulKnowledgeSessionImpl session = (StatefulKnowledgeSessionImpl) ksession; List<Integer> list = new DebugList<Integer>(); ksession.setGlobal( "list", list ); for (int i = 0; i < 10; i++) { session.insertAsync( i ); session.insertAsync( "" + i ); } ksession.fireAllRules(); assertEquals(ruleNr, list.size()); } private String getRule(int i, String rhs) { return getRule( i, rhs, "" ); } private String getRule(int i, String rhs, String attributes) { return "rule R" + i + " " + attributes + "when\n" + " $i : Integer( intValue == " + i + " )" + " String( toString == $i.toString )\n" + "then\n" + " list.add($i);\n" + rhs + "end\n"; } private String getNotRule(int i) { return "rule Rnot" + i + " when\n" + " String( toString == \"" + i + "\" )\n" + " not Integer( intValue == " + i + " )" + "then\n" + " list.add(" + -i + ");\n" + "end\n"; } @Test(timeout = 10000L) public void testFireUntilHalt() { StringBuilder sb = new StringBuilder( 400 ); sb.append( "global java.util.List list;\n" ); for (int i = 0; i < 10; i++) { sb.append( getRule( i, "" ) ); } KieSession ksession = new KieHelper().addContent( sb.toString(), ResourceType.DRL ) .build( MultithreadEvaluationOption.YES ) .newKieSession(); assertTrue( ( (InternalWorkingMemory) ksession ).getAgenda().isParallelAgenda() ); CountDownLatch done = new CountDownLatch(1); DebugList<Integer> list = new DebugList<Integer>(); list.onItemAdded = ( l -> { if (l.size() == 10) { ksession.halt(); done.countDown(); }} ); ksession.setGlobal( "list", list ); new Thread( () -> ksession.fireUntilHalt() ).start(); for (int i = 0; i < 10; i++) { ksession.insert( i ); ksession.insert( "" + i ); } try { done.await(); } catch (InterruptedException e) { throw new RuntimeException( e ); } assertEquals(10, list.size()); } @Test(timeout = 10000L) public void testFireUntilHalt2() { int rulesNr = 4; int factsNr = 1; int fireNr = rulesNr * factsNr; String drl = "import " + A.class.getCanonicalName() + ";\n" + "import " + B.class.getCanonicalName() + ";\n" + "global java.util.concurrent.atomic.AtomicInteger counter\n" + "global java.util.concurrent.CountDownLatch done\n" + "global java.util.List list;\n"; for (int i = 0; i < rulesNr; i++) { drl += getFireUntilHaltRule(fireNr, i); } KieBase kbase = new KieHelper().addContent( drl, ResourceType.DRL ) .build( MultithreadEvaluationOption.YES ); for (int loop = 0; loop < 10; loop++) { System.out.println("Starting loop " + loop); KieSession ksession = kbase.newKieSession(); assertTrue( ( (InternalWorkingMemory) ksession ).getAgenda().isParallelAgenda() ); CountDownLatch done = new CountDownLatch( 1 ); ksession.setGlobal( "done", done ); AtomicInteger counter = new AtomicInteger( 0 ); ksession.setGlobal( "counter", counter ); List<String> list = new DebugList<String>(); ksession.setGlobal( "list", list ); new Thread( () -> { ksession.fireUntilHalt(); } ).start(); A a = new A( rulesNr + 1 ); ksession.insert( a ); for ( int i = 0; i < factsNr; i++ ) { ksession.insert( new B( rulesNr + i + 3 ) ); } try { done.await(); } catch (InterruptedException e) { throw new RuntimeException( e ); } assertEquals( fireNr, counter.get() ); ksession.halt(); ksession.dispose(); System.out.println("Loop " + loop + " terminated"); } } private String getFireUntilHaltRule(int fireNr, int i) { return "rule R" + i + " when\n" + " A( $a : value > " + i + ")\n" + " B( $b : value > $a )\n" + "then\n" + " list.add( drools.getRule().getName() );" + " if (counter.incrementAndGet() == " + fireNr + " ) {\n" + " drools.halt();\n" + " done.countDown();\n" + " }\n" + "end\n"; } public static class A { private int value; public A( int value ) { this.value = value; } public int getValue() { return value; } public void setValue( int value ) { this.value = value; } } public static class B { private int value; public B( int value ) { this.value = value; } public int getValue() { return value; } public void setValue( int value ) { this.value = value; } } @Test(timeout = 10000L) public void testFireUntilHaltWithAsyncInsert() { StringBuilder sb = new StringBuilder( 400 ); sb.append( "global java.util.List list;\n" ); for (int i = 0; i < 10; i++) { sb.append( getRule( i, "" ) ); } KieSession ksession = new KieHelper().addContent( sb.toString(), ResourceType.DRL ) .build( MultithreadEvaluationOption.YES ) .newKieSession(); assertTrue( ( (InternalWorkingMemory) ksession ).getAgenda().isParallelAgenda() ); StatefulKnowledgeSessionImpl session = (StatefulKnowledgeSessionImpl) ksession; CountDownLatch done = new CountDownLatch(1); DebugList<Integer> list = new DebugList<Integer>(); list.onItemAdded = ( l -> { if (l.size() == 10) { ksession.halt(); done.countDown(); }} ); ksession.setGlobal( "list", list ); new Thread( () -> ksession.fireUntilHalt() ).start(); for (int i = 0; i < 10; i++) { session.insertAsync( i ); session.insertAsync( "" + i ); } try { done.await(); } catch (InterruptedException e) { throw new RuntimeException( e ); } assertEquals(10, list.size()); } @Test(timeout = 10000L) public void testDisableParallelismOnSinglePartition() { String drl = "rule R1 when\n" + " $i : Integer( this == 4 )" + " String( length > $i )\n" + "then end \n" + "rule R2 when\n" + " $i : Integer( this == 4 )" + " String( length == $i )\n" + "then end \n" + "rule R3 when\n" + " $i : Integer( this == 4 )" + " String( length < $i )\n" + "then end \n"; KieSession ksession = new KieHelper().addContent( drl, ResourceType.DRL ) .build( MultithreadEvaluationOption.YES ) .newKieSession(); InternalWorkingMemory session = (InternalWorkingMemory) ksession; // since there is only one partition the multithread evaluation should be disabled and run with the DefaultAgenda assertFalse( ( (InternalWorkingMemory) ksession ).getAgenda().isParallelAgenda() ); } @Test(timeout = 10000L) public void testEventsExpiration() { StringBuilder sb = new StringBuilder( 400 ); sb.append( "global java.util.List list;\n" ); sb.append( "import " + MyEvent.class.getCanonicalName() + ";\n" ); sb.append( "declare MyEvent @role( event ) @expires( 20ms ) @timestamp( timestamp ) end\n" ); for (int i = 0; i < 10; i++) { sb.append( getRuleWithEvent( i ) ); } KieSessionConfiguration sessionConfig = KnowledgeBaseFactory.newKnowledgeSessionConfiguration(); sessionConfig.setOption( ClockTypeOption.get( ClockType.PSEUDO_CLOCK.getId() ) ); KieSession ksession = new KieHelper().addContent( sb.toString(), ResourceType.DRL ) .build( EventProcessingOption.STREAM, MultithreadEvaluationOption.YES ) .newKieSession( sessionConfig, null ); assertTrue( ( (InternalWorkingMemory) ksession ).getAgenda().isParallelAgenda() ); PseudoClockScheduler sessionClock = ksession.getSessionClock(); sessionClock.setStartupTime(0); List<Integer> list = new DebugList<Integer>(); ksession.setGlobal( "list", list ); for (int i = 0; i < 10; i++) { ksession.insert( new MyEvent( i, i*2L ) ); } ksession.fireAllRules(); assertEquals(10, list.size()); assertEquals( 10L, ksession.getFactCount() ); sessionClock.advanceTime( 29, TimeUnit.MILLISECONDS ); ksession.fireAllRules(); assertEquals( 5L, ksession.getFactCount() ); sessionClock.advanceTime( 12, TimeUnit.MILLISECONDS ); ksession.fireAllRules(); assertEquals( 0L, ksession.getFactCount() ); } @Test(timeout = 10000L) public void testImmediateEventsExpiration() { StringBuilder sb = new StringBuilder( 400 ); sb.append( "global java.util.List list;\n" ); sb.append( "import " + MyEvent.class.getCanonicalName() + ";\n" ); sb.append( "declare MyEvent @role( event ) @expires( 1ms ) @timestamp( timestamp ) end\n" ); for (int i = 0; i < 10; i++) { sb.append( getRuleWithEvent( i ) ); } KieSessionConfiguration sessionConfig = KnowledgeBaseFactory.newKnowledgeSessionConfiguration(); KieSession ksession = new KieHelper().addContent( sb.toString(), ResourceType.DRL ) .build( EventProcessingOption.STREAM, MultithreadEvaluationOption.YES ) .newKieSession( sessionConfig, null ); assertTrue( ( (InternalWorkingMemory) ksession ).getAgenda().isParallelAgenda() ); List<Integer> list = new DebugList<Integer>(); ksession.setGlobal( "list", list ); for (int i = 0; i < 10; i++) { ksession.insert( new MyEvent( i, i*2L ) ); } ksession.fireAllRules(); assertEquals(10, list.size()); } public static class MyEvent { private final int id; private final long timestamp; public MyEvent( int id, long timestamp ) { this.id = id; this.timestamp = timestamp; } public long getTimestamp() { return timestamp; } public int getId() { return id; } @Override public String toString() { return "MyEvent[" + id + "]"; } } private String getRuleWithEvent(int i) { return "rule R" + i + " when\n" + " $i : MyEvent( id == " + i + " )" + "then\n" + " list.add($i);\n" + "end\n"; } private String getRuleWithEventForExpiration(int i) { return "rule R" + i + " when\n" + " $i : MyEvent( id == " + i + " )\n" + "then\n" + " list.add($i);\n" + " insert(" + i + ");\n" + "end\n" + "rule R" + i + "not when\n" + " Integer( this == " + i + " )\n" + " not MyEvent( id == " + i + " )\n" + "then\n" + " list.add(" + i + ");\n" + "end\n"; } @Test(timeout = 10000L) public void testFireUntilHaltWithExpiration() { StringBuilder sb = new StringBuilder( 400 ); sb.append( "global java.util.List list;\n" ); sb.append( "import " + MyEvent.class.getCanonicalName() + ";\n" ); sb.append( "declare MyEvent @role( event ) @expires( 20ms ) @timestamp( timestamp ) end\n" ); for (int i = 0; i < 10; i++) { sb.append( getRuleWithEventForExpiration( i ) ); } KieSessionConfiguration sessionConfig = KnowledgeBaseFactory.newKnowledgeSessionConfiguration(); sessionConfig.setOption( ClockTypeOption.get( ClockType.PSEUDO_CLOCK.getId() ) ); KieSession ksession = new KieHelper().addContent( sb.toString(), ResourceType.DRL ) .build( EventProcessingOption.STREAM, MultithreadEvaluationOption.YES ) .newKieSession( sessionConfig, null ); assertTrue( ( (InternalWorkingMemory) ksession ).getAgenda().isParallelAgenda() ); PseudoClockScheduler sessionClock = ksession.getSessionClock(); sessionClock.setStartupTime(0); DebugList<Integer> list = new DebugList<Integer>(); CountDownLatch done1 = new CountDownLatch(1); list.onItemAdded = ( l -> { if (l.size() == 10) { done1.countDown(); }} ); ksession.setGlobal( "list", list ); for (int i = 0; i < 10; i++) { ksession.insert( new MyEvent( i, i*2L ) ); } new Thread( () -> ksession.fireUntilHalt() ).start(); try { done1.await(); } catch (InterruptedException e) { throw new RuntimeException( e ); } assertEquals( 10, list.size() ); list.clear(); CountDownLatch done2 = new CountDownLatch(1); list.onItemAdded = ( l -> { if (l.size() == 5) { done2.countDown(); }} ); ksession.insert( 1 ); sessionClock.advanceTime( 29, TimeUnit.MILLISECONDS ); try { done2.await(); } catch (InterruptedException e) { throw new RuntimeException( e ); } assertEquals( 5, list.size() ); list.clear(); CountDownLatch done3 = new CountDownLatch(1); list.onItemAdded = ( l -> { if (l.size() == 5) { done3.countDown(); }} ); sessionClock.advanceTime( 12, TimeUnit.MILLISECONDS ); try { done3.await(); } catch (InterruptedException e) { throw new RuntimeException( e ); } assertEquals( 5, list.size() ); ksession.halt(); ksession.dispose(); } @Test(timeout = 100000L) public void testFireUntilHaltWithExpiration2() { String drl = "import " + A.class.getCanonicalName() + "\n" + "import " + B.class.getCanonicalName() + "\n" + "declare A @role( event ) @expires(11ms) end\n" + "declare B @role( event ) @expires(11ms) end\n" + "global java.util.concurrent.atomic.AtomicInteger counter;\n" + "rule R0 when\n" + " $A: A( $Aid : value > 0 )\n" + " $B: B( ($Bid: value <= $Aid) && (value > ($Aid - 1 )))\n" + "then\n" + " counter.incrementAndGet();\n" + "end\n" + "rule R1 when\n" + " $A: A( $Aid: value > 1 )\n" + " $B: B( ($Bid: value <= $Aid) && (value > ($Aid - 1 )))\n" + "then\n" + " counter.incrementAndGet();\n" + "end\n" + "rule R2 when\n" + " $A: A( $Aid: value > 2 )\n" + " $B: B( ($Bid: value <= $Aid) && (value > ($Aid - 1 )))\n" + "then\n" + " counter.incrementAndGet();\n" + "end\n" + "rule R3 when\n" + " $A: A( $Aid: value > 3 )\n" + " $B: B( ($Bid: value <= $Aid) && (value > ($Aid - 1 )))\n" + "then\n" + " counter.incrementAndGet();\n" + "end"; KieSessionConfiguration sessionConfig = KnowledgeBaseFactory.newKnowledgeSessionConfiguration(); sessionConfig.setOption( ClockTypeOption.get( ClockType.PSEUDO_CLOCK.getId() ) ); KieSession ksession = new KieHelper().addContent( drl, ResourceType.DRL ) .build( EventProcessingOption.STREAM, MultithreadEvaluationOption.YES ) .newKieSession( sessionConfig, null ); assertTrue( ( (InternalWorkingMemory) ksession ).getAgenda().isParallelAgenda() ); PseudoClockScheduler sessionClock = ksession.getSessionClock(); sessionClock.setStartupTime( 0 ); AtomicInteger counter = new AtomicInteger( 0 ); ksession.setGlobal( "counter", counter ); new Thread( () -> ksession.fireUntilHalt() ).start(); int eventsNr = 5; for ( int i = 0; i < eventsNr; i++ ) { ksession.insert( new A( i + 4 ) ); ksession.insert( new B( i + 4 ) ); sessionClock.advanceTime( 10, TimeUnit.MILLISECONDS ); } try { Thread.sleep( 1000L ); } catch (InterruptedException e) { throw new RuntimeException( e ); } ksession.halt(); ksession.dispose(); assertEquals( eventsNr * 4, counter.get() ); } @Test(timeout = 10000L) public void testWithUpdates() { StringBuilder sb = new StringBuilder( 400 ); sb.append( "global java.util.List list;\n" ); for (int i = 0; i < 10; i++) { sb.append( getRule( i, "" ) ); } KieSession ksession = new KieHelper().addContent( sb.toString(), ResourceType.DRL ) .build( MultithreadEvaluationOption.YES ) .newKieSession(); assertTrue( ( (InternalWorkingMemory) ksession ).getAgenda().isParallelAgenda() ); List<Integer> list = new DebugList<Integer>(); ksession.setGlobal( "list", list ); FactHandle[] fhs = new FactHandle[10]; for (int i = 0; i < 10; i++) { fhs[i] = ksession.insert( i ); ksession.insert( "" + i ); } ksession.fireAllRules(); assertEquals(10, list.size()); list.clear(); for (int i = 0; i < 10; i++) { ksession.update( fhs[i], i ); } ksession.fireAllRules(); assertEquals(10, list.size()); } @Test(timeout = 10000L) public void testDisableParallelismWithAgendaGroups() { StringBuilder sb = new StringBuilder( 400 ); sb.append( "global java.util.List list;\n" ); sb.append( "rule first\n" + "when\n" + "then\n" + " drools.getKnowledgeRuntime().getAgenda().getAgendaGroup(\"agenda\").setFocus();\n" + "end\n" ); for (int i = 0; i < 10; i++) { sb.append( getRule( i, "", "agenda-group \"agenda\"" ) ); } KieBase kbase = new KieHelper().addContent( sb.toString(), ResourceType.DRL ) .build( MultithreadEvaluationOption.YES ); KieSession ksession = kbase.newKieSession(); // multithread evaluation is not allowed when using agenda-groups assertFalse( ( (InternalWorkingMemory) ksession ).getAgenda().isParallelAgenda() ); List<Integer> list = new DebugList<Integer>(); ksession.setGlobal( "list", list ); for (int i = 0; i < 10; i++) { ksession.insert( i ); ksession.insert( "" + i ); } ksession.fireAllRules(); assertEquals(10, list.size()); } @Test(timeout = 10000L) public void testDisableParallelismWithSalience() { StringBuilder sb = new StringBuilder( 400 ); sb.append( "global java.util.List list;\n" ); for (int i = 0; i < 10; i++) { sb.append( getRule( i, "", "salience " + i ) ); } KieBase kbase = new KieHelper().addContent( sb.toString(), ResourceType.DRL ) .build( MultithreadEvaluationOption.YES ); KieSession ksession = kbase.newKieSession(); // multithread evaluation is not allowed when using salience assertFalse( ( (InternalWorkingMemory) ksession ).getAgenda().isParallelAgenda() ); List<Integer> list = new DebugList<Integer>(); ksession.setGlobal( "list", list ); for (int i = 0; i < 10; i++) { ksession.insert( i ); ksession.insert( "" + i ); } ksession.fireAllRules(); assertEquals(10, list.size()); assertEquals( list, Arrays.asList(9, 8, 7, 6, 5, 4, 3, 2, 1, 0) ); } @Test(timeout = 10000L) public void testMultipleParallelKieSessionsWithInsertions() throws InterruptedException, ExecutionException, TimeoutException { final int NUMBER_OF_PARALLEL_SESSIONS = 5; /* Create KIE base */ StringBuilder sb = new StringBuilder(); sb.append("global java.util.List list;\n"); final int ruleNr = 200; for (int i = 0; i < ruleNr; i++) { sb.append(getRule(i, "insert( $i + 10 );\ninsert( \"\" + ($i + 10) );\n")); } KieBase kBase = new KieHelper().addContent(sb.toString(), ResourceType.DRL) .build(MultithreadEvaluationOption.YES); /* Create parallel tasks */ List<Callable<Void>> tasks = new ArrayList<>(); for (int i = 0; i < NUMBER_OF_PARALLEL_SESSIONS; i++) { tasks.add(getMultipleParallelKieSessionsWithInsertionsCallable(kBase, ruleNr)); } runTasksInParallel(tasks); } private Callable<Void> getMultipleParallelKieSessionsWithInsertionsCallable(KieBase kBase, int ruleNr) { return new Callable<Void>() { @Override public Void call() { KieSession ksession = kBase.newKieSession(); assertTrue( ( (InternalWorkingMemory) ksession ).getAgenda().isParallelAgenda() ); List<Integer> list = new DebugList<Integer>(); ksession.setGlobal( "list", list ); insertFacts(ksession, 10); ksession.fireAllRules(); assertEquals(ruleNr, list.size()); return null; } }; } @Test(timeout = 10000L) public void testMultipleParallelKieSessionsWithUpdates() throws InterruptedException, ExecutionException, TimeoutException { final int NUMBER_OF_PARALLEL_SESSIONS = 5; /* Create KIE base */ StringBuilder sb = new StringBuilder( 400 ); sb.append( "global java.util.List list;\n" ); for (int i = 0; i < 10; i++) { sb.append( getRule( i, "" ) ); } KieBase kBase = new KieHelper().addContent(sb.toString(), ResourceType.DRL) .build(MultithreadEvaluationOption.YES); /* Create parallel tasks */ List<Callable<Void>> tasks = new ArrayList<>(); for (int i = 0; i < NUMBER_OF_PARALLEL_SESSIONS; i++) { tasks.add(getMultipleParallelKieSessionsWithUpdatesCallable(kBase)); } /* Run tasks in parallel */ runTasksInParallel(tasks); } private Callable<Void> getMultipleParallelKieSessionsWithUpdatesCallable(KieBase kBase) { return new Callable<Void>() { @Override public Void call() { KieSession ksession = kBase.newKieSession(); assertThat(((InternalWorkingMemory) ksession).getAgenda().isParallelAgenda()).as("Parallel agenda has to be enabled").isTrue(); List<Integer> list = new DebugList<Integer>(); ksession.setGlobal( "list", list ); FactHandle[] fhs = new FactHandle[10]; fhs = insertFacts(ksession, 10); ksession.fireAllRules(); assertThat(list.size()).isEqualTo(10); list.clear(); for (int i = 0; i < 10; i++) { ksession.update( fhs[i], i ); } ksession.fireAllRules(); assertThat(list.size()).isEqualTo(10); return null; } }; } @Test(timeout = 10000L) public void testMultipleParallelKieSessionsWithDeletes() throws InterruptedException, ExecutionException, TimeoutException { final int NUMBER_OF_PARALLEL_SESSIONS = 5; /* Create KIE base */ StringBuilder sb = new StringBuilder(400); sb.append("global java.util.List list;\n"); for (int i = 1; i < 11; i++) { sb.append(getRule(i, "delete( $i );\n")); } for (int i = 1; i < 11; i++) { sb.append(getNotRule(i)); } KieBase kbase = new KieHelper().addContent(sb.toString(), ResourceType.DRL) .build(MultithreadEvaluationOption.YES); List<Callable<Void>> tasks = new ArrayList<>(); for (int i = 0; i < NUMBER_OF_PARALLEL_SESSIONS; i++) { tasks.add(getMultipleParallelKieSessionsWithDeletesCallable(kbase)); } /* Run tasks in parallel */ runTasksInParallel(tasks); } private Callable<Void> getMultipleParallelKieSessionsWithDeletesCallable(KieBase kbase) { return new Callable<Void>() { @Override public Void call() { KieSession ksession = kbase.newKieSession(); assertThat(((InternalWorkingMemory) ksession).getAgenda().isParallelAgenda()).isTrue(); List<Integer> list = new DebugList<Integer>(); ksession.setGlobal( "list", list ); insertFacts(ksession, 11); ksession.fireAllRules(); assertThat(ksession.getObjects()).isEmpty(); assertThat(list.size()).isEqualTo(20); return null; } }; } @Test(timeout = 10000L) public void testMultipleParallelKieSessionsFireUntilHalt() throws InterruptedException, ExecutionException, TimeoutException { final int NUMBER_OF_PARALLEL_SESSIONS = 5; /* Create KIE base */ StringBuilder sb = new StringBuilder(400); sb.append("global java.util.List list;\n"); for (int i = 0; i < 10; i++) { sb.append(getRule(i, "")); } KieBase kbase = new KieHelper().addContent(sb.toString(), ResourceType.DRL) .build(MultithreadEvaluationOption.YES); List<Callable<Void>> tasks = new ArrayList<>(); for (int i = 0; i < NUMBER_OF_PARALLEL_SESSIONS; i++) { tasks.add(getMultipleParallelKieSessionsFireUntilHaltCallable(kbase, false)); } /* Run tasks in parallel */ runTasksInParallel(tasks); } private Callable<Void> getMultipleParallelKieSessionsFireUntilHaltCallable(KieBase kBase, boolean asyncInsert) { return () -> { KieSession ksession = kBase.newKieSession(); assertThat(((InternalWorkingMemory) ksession).getAgenda().isParallelAgenda()).isTrue(); CountDownLatch done = new CountDownLatch(1); DebugList<Integer> list = new DebugList<Integer>(); list.onItemAdded = (l -> { if (l.size() == 10) { ksession.halt(); done.countDown(); } }); ksession.setGlobal("list", list); new Thread(ksession::fireUntilHalt).start(); if (asyncInsert) { StatefulKnowledgeSessionImpl session = (StatefulKnowledgeSessionImpl) ksession; for (int i = 0; i < 10; i++) { session.insertAsync(i); session.insertAsync("" + String.valueOf(i)); } } else { insertFacts(ksession, 10); } try { done.await(); } catch (InterruptedException e) { throw new RuntimeException(e); } assertThat(list.size()).isEqualTo(10); return null; }; } @Test(timeout = 10000L) public void testMultipleParallelKieSessionsFireUntilHaltWithAsyncInsert() throws InterruptedException, ExecutionException, TimeoutException { final int NUMBER_OF_PARALLEL_SESSIONS = 5; /* Create KIE base */ StringBuilder sb = new StringBuilder(400); sb.append("global java.util.List list;\n"); for (int i = 0; i < 10; i++) { sb.append(getRule(i, "")); } KieBase kbase = new KieHelper().addContent(sb.toString(), ResourceType.DRL) .build(MultithreadEvaluationOption.YES); /* Create parallel tasks */ List<Callable<Void>> tasks = new ArrayList<>(); for (int i = 0; i < NUMBER_OF_PARALLEL_SESSIONS; i++) { tasks.add(getMultipleParallelKieSessionsFireUntilHaltCallable(kbase, true)); } /* Run tasks in parallel */ runTasksInParallel(tasks); } private FactHandle[] insertFacts(KieSession ksession, int n) { FactHandle[] fhs = new FactHandle[n]; for (int i = 0; i < n; i++) { fhs[i] = ksession.insert(i); ksession.insert(String.valueOf(i)); } return fhs; } private void runTasksInParallel(List<Callable<Void>> tasks) throws InterruptedException, TimeoutException, ExecutionException { ExecutorService executorService = Executors.newFixedThreadPool(tasks.size()); try { List<Future<Void>> futures = executorService.invokeAll(tasks); assertThat(futures.size()).isEqualTo(tasks.size()); } finally { executorService.shutdownNow(); } } }