/* * Copyright 2008 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. * * Created on Feb 5, 2008 */ package org.drools.compiler.integrationtests; import org.drools.compiler.CommonTestMethodBase; import org.drools.compiler.StockTick; import org.junit.Ignore; import org.junit.Test; import org.kie.api.KieBase; import org.kie.api.KieBaseConfiguration; import org.kie.api.KieServices; 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.conf.TimedRuleExecutionOption; import org.kie.api.runtime.rule.EntryPoint; import org.kie.api.runtime.rule.FactHandle; import org.kie.api.runtime.rule.QueryResults; import org.kie.api.runtime.rule.Variable; import org.kie.internal.KnowledgeBase; import org.kie.internal.KnowledgeBaseFactory; import org.kie.internal.builder.KnowledgeBuilder; import org.kie.internal.builder.KnowledgeBuilderFactory; import org.kie.internal.io.ResourceFactory; import org.kie.internal.runtime.StatefulKnowledgeSession; import org.kie.internal.utils.KieHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.CompletionService; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorCompletionService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; /** * This is a test case for multi-thred issues */ public class MultithreadTest extends CommonTestMethodBase { private static final Logger LOG = LoggerFactory.getLogger(MultithreadTest.class); @Test(timeout = 10000) public void testConcurrentInsertionsFewObjectsManyThreads() { final String drl = "import org.drools.compiler.integrationtests.MultithreadTest.Bean\n" + "\n" + "rule \"R\"\n" + "when\n" + " $a : Bean( seed != 1 )\n" + "then\n" + "end"; testConcurrentInsertions(drl, 1, 1000, false, false); } @Test(timeout = 10000) public void testConcurrentInsertionsManyObjectsFewThreads() { final String drl = "import org.drools.compiler.integrationtests.MultithreadTest.Bean\n" + "\n" + "rule \"R\"\n" + "when\n" + " $a : Bean( seed != 1 )\n" + "then\n" + "end"; testConcurrentInsertions(drl, 1000, 4, false, false); } @Test(timeout = 10000) public void testConcurrentInsertionsNewSessionEachThreadObjectTypeNode() { final String drl = "import org.drools.compiler.integrationtests.MultithreadTest.Bean\n" + " query existsBeanSeed5More() \n" + " Bean( seed > 5 ) \n" + " end \n" + "\n" + "rule \"R\"\n" + "when\n" + " $a: Bean( seed != 1 )\n" + " existsBeanSeed5More() \n" + "then\n" + "end \n" + "rule \"R2\"\n" + "when\n" + " $a: Bean( seed != 1 )\n" + "then\n" + "end\n"; testConcurrentInsertions(drl, 10, 1000, true, true); } @Test(timeout = 10000) public void testConcurrentInsertionsNewSessionEachThread() { final String drl = "import org.drools.compiler.integrationtests.MultithreadTest.Bean\n" + " query existsBeanSeed5More() \n" + " Bean( seed > 5 ) \n" + " end \n" + "\n" + "rule \"R\"\n" + "when\n" + " $a: Bean( seed != 1 )\n" + " $b: Bean( seed != 2 )\n" + " existsBeanSeed5More() \n" + "then\n" + "end \n" + "rule \"R2\"\n" + "when\n" + " $a: Bean( seed != 1 )\n" + " $b: Bean( seed != 2 )\n" + "then\n" + "end\n" + "rule \"R3\"\n" + "when\n" + " $a: Bean( seed != 3 )\n" + " $b: Bean( seed != 4 )\n" + " $c: Bean( seed != 5 )\n" + " $d: Bean( seed != 6 )\n" + " $e: Bean( seed != 7 )\n" + "then\n" + "end"; testConcurrentInsertions(drl, 10, 1000, true, false); } private void testConcurrentInsertions(final String drl, final int objectCount, final int threadCount, final boolean newSessionForEachThread, final boolean updateFacts) { final KieBase kieBase = new KieHelper().addContent(drl, ResourceType.DRL).build(); Executor executor = Executors.newCachedThreadPool(new ThreadFactory() { public Thread newThread(Runnable r) { Thread t = new Thread(r); t.setDaemon(true); return t; } }); KieSession ksession = null; Callable<Boolean>[] tasks = new Callable[threadCount]; if (newSessionForEachThread) { for (int i = 0; i < threadCount; i++) { tasks[i] = getTask( objectCount, kieBase, updateFacts ); } } else { ksession = kieBase.newKieSession(); for (int i = 0; i < threadCount; i++) { tasks[i] = getTask( objectCount, ksession, false , updateFacts ); } } CompletionService<Boolean> ecs = new ExecutorCompletionService<Boolean>(executor); for (Callable<Boolean> task : tasks) { ecs.submit( task ); } int successCounter = 0; for (int i = 0; i < threadCount; i++) { try { if ( ecs.take().get() ) { successCounter++; } } catch (Exception e) { throw new RuntimeException(e); } } assertEquals(threadCount, successCounter); if (ksession != null) { ksession.dispose(); } } private Callable<Boolean> getTask( final int objectCount, final KieBase kieBase, final boolean updateFacts) { return getTask(objectCount, kieBase.newKieSession(), true, updateFacts); } private Callable<Boolean> getTask( final int objectCount, final KieSession ksession, final boolean disposeSession, final boolean updateFacts) { return new Callable<Boolean>() { public Boolean call() throws Exception { try { for (int j = 0; j < 10; j++) { FactHandle[] facts = new FactHandle[objectCount]; for (int i = 0; i < objectCount; i++) { facts[i] = ksession.insert(new Bean(i)); } if (updateFacts) { for (int i = 0; i < objectCount; i++) { ksession.update(facts[i], new Bean(-i)); } } for (FactHandle fact : facts) { ksession.delete(fact); } ksession.fireAllRules(); } return true; } catch (Exception e) { e.printStackTrace(); return false; } finally { if (disposeSession) { ksession.dispose(); } } } }; } public static class Bean { private final int seed; private final String threadName; public Bean(int seed) { this.seed = seed; threadName = Thread.currentThread().getName(); } public int getSeed() { return seed; } @Override public boolean equals(Object other) { if (!(other instanceof Bean)) return false; return seed == ((Bean)other).seed && threadName.equals( ((Bean)other).threadName ); } @Override public int hashCode() { return 29 * seed + 31 * threadName.hashCode(); } @Override public String toString() { return "Bean #" + seed + " created by " + threadName; } } /*/ public static class Bean { private final int seed; public Bean(int seed) { this.seed = seed; } public int getSeed() { return seed; } @Override public boolean equals(Object other) { if (!(other instanceof Bean)) return false; return seed == ((Bean)other).seed; } @Override public int hashCode() { return seed; } @Override public String toString() { return "Bean #" + seed; } } */ @Test(timeout = 1000000) public void testSlidingTimeWindows() { String str = "package org.drools\n" + "global java.util.List list; \n" + "import org.drools.compiler.StockTick; \n" + "" + "declare StockTick @role(event) end\n" + "" + "rule R\n" + "when\n" + " accumulate( $st : StockTick() over window:time(400ms)\n" + " from entry-point X,\n" + " $c : count(1) )" + "then\n" + " list.add( $c ); \n" + "end \n"; final List<Exception> errors = new ArrayList<Exception>( ); KieBaseConfiguration kbconf = KnowledgeBaseFactory.newKnowledgeBaseConfiguration(); kbconf.setOption(EventProcessingOption.STREAM); KnowledgeBase kbase = loadKnowledgeBaseFromString( kbconf, str ); final StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession(); final EntryPoint ep = ksession.getEntryPoint( "X" ); List list = new ArrayList(); ksession.setGlobal( "list", list ); Executor executor = Executors.newCachedThreadPool(new ThreadFactory() { public Thread newThread(Runnable r) { Thread t = new Thread(r); t.setDaemon(true); return t; } }); final int RUN_TIME = 5000; // runs for 10 seconds final int THREAD_NR = 2; CompletionService<Boolean> ecs = new ExecutorCompletionService<Boolean>(executor); ecs.submit(new Callable<Boolean>() { public Boolean call() throws Exception { try { ksession.fireUntilHalt(); return true; } catch (Exception e) { errors.add( e ); e.printStackTrace(); return false; } } }); for (int i = 0; i < THREAD_NR; i++) { ecs.submit(new Callable<Boolean>() { public Boolean call() throws Exception { try { final String s = Thread.currentThread().getName(); long endTS = System.currentTimeMillis() + RUN_TIME; int j = 0; long lastTimeInserted = -1; while( System.currentTimeMillis() < endTS ) { final long currentTimeInMillis = System.currentTimeMillis(); if ( currentTimeInMillis > lastTimeInserted ) { lastTimeInserted = currentTimeInMillis; ep.insert( new StockTick( j++, s, 0.0, 0 ) ); } } return true; } catch ( Exception e ) { errors.add( e ); e.printStackTrace(); return false; } } }); } boolean success = true; for (int i = 0; i < THREAD_NR; i++) { try { success = ecs.take().get() && success; } catch (Exception e) { errors.add( e ); } } ksession.halt(); try { success = ecs.take().get() && success; } catch (Exception e) { errors.add( e ); } for ( Exception e : errors ) { e.printStackTrace(); } assertTrue( errors.isEmpty() ); assertTrue( success ); ksession.dispose(); } @Test( timeout = 10000 ) public void testClassLoaderRace() throws InterruptedException { String drl = "package org.drools.integrationtests;\n" + "" + "rule \"average temperature\"\n" + "when\n" + " $avg := Number( ) from accumulate ( " + " $x : Integer ( ); " + " average ($x) )\n" + "then\n" + " System.out.println( $avg );\n" + "end\n" + "\n"; KnowledgeBase kbase = loadKnowledgeBaseFromString(drl); final StatefulKnowledgeSession session = kbase.newStatefulKnowledgeSession(); Thread t = new Thread() { public void run() { session.fireUntilHalt(); } }; t.start(); session.fireAllRules(); for ( int j = 0; j < 100; j++ ) { session.insert( j ); } try { Thread.sleep( 1000 ); System.out.println( "Halting .." ); session.halt(); } catch ( Exception e ) { fail( e.getMessage() ); } } public static class IntEvent { private int data; public IntEvent( int j ) { data = j; } public int getData() { return data; } public void setData( int data ) { this.data = data; } } public class Server { public int currentTemp; public double avgTemp; public String hostname; public int readingCount; public Server( String hiwaesdk ) { hostname = hiwaesdk; } public String toString() { return "Server{" + "currentTemp=" + currentTemp + ", avgTemp=" + avgTemp + ", hostname='" + hostname + '\'' + '}'; } } @Test(timeout = 5000) public void testRaceOnAccumulateNodeSimple() throws InterruptedException { String drl = "package org.drools.integrationtests;\n" + "" + "import org.drools.compiler.integrationtests.MultithreadTest.IntEvent; \n" + "import org.drools.compiler.integrationtests.MultithreadTest.Server; \n" + "" + "declare IntEvent\n" + " @role ( event )\n" + " @expires( 15s )\n" + "end\n" + "\n" + "" + "rule \"average temperature\"\n" + "when\n" + " $s : Server (hostname == \"hiwaesdk\")\n" + " $avg := Number( ) from accumulate ( " + " IntEvent ( $temp : data ) over window:length(10) from entry-point ep01; " + " average ($temp)\n" + " )\n" + "then\n" + " $s.avgTemp = $avg.intValue();\n" + " System.out.println( $avg );\n" + "end\n" + "\n"; KieBaseConfiguration kbconfig = KnowledgeBaseFactory.newKnowledgeBaseConfiguration(); kbconfig.setOption(EventProcessingOption.STREAM); KnowledgeBase kbase = loadKnowledgeBaseFromString(kbconfig, drl); final StatefulKnowledgeSession session = kbase.newStatefulKnowledgeSession(); EntryPoint ep01 = session.getEntryPoint("ep01"); Runner t = new Runner(session); t.start(); Thread.sleep( 1000 ); Server hiwaesdk = new Server ("hiwaesdk"); session.insert( hiwaesdk ); long LIMIT = 20; for ( long i = LIMIT; i > 0; i-- ) { ep01.insert ( new IntEvent ( (int) i ) ); //Thread.sleep (0x1); } if ( i % 1000 == 0 ) { System.out.println( i ); } } try { Thread.sleep( 1000 ); System.out.println( "Halting .." ); session.halt(); } catch ( Exception e ) { fail( e.getMessage() ); } if (t.getError() != null) { fail(t.getError().getMessage()); } } public static class MyFact { Date timestamp = new Date(); String id = UUID.randomUUID().toString(); public MyFact() {} public String getId() { return id; } public void setId(String id) { this.id = id; } } @Test @Ignore public void testConcurrencyWithChronThreads() throws InterruptedException { String drl = "package it.intext.drools.fusion.bug;\n" + "\n" + "import org.drools.compiler.integrationtests.MultithreadTest.MyFact;\n" + " global java.util.List list; \n" + "\n" + "declare MyFact\n" + "\t@role( event )\n" + "\t@expires( 1s )\n" + "end\n" + "\n" + "rule \"Dummy\"\n" + "timer( cron: 0/1 * * * * ? )\n" + "when\n" + " Number( $count : intValue ) from accumulate( MyFact( ) over window:time(1s); sum(1) )\n" + "then\n" + " System.out.println($count+\" myfact(s) seen in the last 1 seconds\");\n" + " list.add( $count ); \n" + "end"; KieBaseConfiguration kbconfig = KnowledgeBaseFactory.newKnowledgeBaseConfiguration(); kbconfig.setOption(EventProcessingOption.STREAM); KnowledgeBase kbase = loadKnowledgeBaseFromString(kbconfig, drl); KieSessionConfiguration conf = KnowledgeBaseFactory.newKnowledgeSessionConfiguration(); conf.setOption( ClockTypeOption.get("REALTIME")); final StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession(conf,null); List list = new ArrayList(); ksession.setGlobal( "list", list ); ksession.fireAllRules(); Runner t = new Runner(ksession); t.start(); final int FACTS_PER_POLL = 1000; final int POLL_INTERVAL = 500; ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); executor.scheduleAtFixedRate( new Runnable() { public void run() { for ( int j = 0; j < FACTS_PER_POLL; j++ ) { ksession.insert( new MyFact() ); } } }, 0, POLL_INTERVAL, TimeUnit.MILLISECONDS ); Thread.sleep( 10200 ); executor.shutdownNow(); ksession.halt(); t.join(); if (t.getError() != null) { fail(t.getError().getMessage()); } System.out.println( "Final size " + ksession.getObjects().size() ); //assertEquals( 2000, ksession.getObjects().size() ); ksession.dispose(); } public static class Runner extends Thread { private final StatefulKnowledgeSession ksession; private Throwable error; public Runner(StatefulKnowledgeSession ksession) { this.ksession = ksession; } @Override public void run() { try { ksession.fireUntilHalt(); } catch (Throwable t) { error = t; throw new RuntimeException(t); } } public Throwable getError() { return error; } } @Test( timeout = 5000 ) public void testConcurrentQueries() { // DROOLS-175 StringBuilder drl = new StringBuilder( ); drl.append( "package org.drools.test;\n" + "" + "query foo( ) \n" + " Object() from new Object() \n" + "end\n" + "" + "rule XYZ when then end \n" ); KnowledgeBuilder knowledgeBuilder = KnowledgeBuilderFactory.newKnowledgeBuilder(); knowledgeBuilder.add( ResourceFactory.newByteArrayResource( drl.toString().getBytes() ), ResourceType.DRL ); final KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase(); kbase.addKnowledgePackages( knowledgeBuilder.getKnowledgePackages() ); final StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession(); Executor executor = Executors.newCachedThreadPool(new ThreadFactory() { public Thread newThread(Runnable r) { Thread t = new Thread(r); t.setDaemon(true); return t; } }); final int THREAD_NR = 5; CompletionService<Boolean> ecs = new ExecutorCompletionService<Boolean>(executor); for (int i = 0; i < THREAD_NR; i++) { ecs.submit(new Callable<Boolean>() { public Boolean call() throws Exception { boolean succ = false; try { QueryResults res = ksession.getQueryResults( "foo", Variable.v ); succ = (res.size() == 1); return succ; } catch (Exception e) { e.printStackTrace(); return succ; } } }); } boolean success = true; for (int i = 0; i < THREAD_NR; i++) { try { success = ecs.take().get() && success; } catch (Exception e) { throw new RuntimeException(e); } } assertTrue(success); ksession.dispose(); } @Test public void testConcurrentDelete() { String drl = "import " + SlowBean.class.getCanonicalName() + ";\n" + "rule R when\n" + " $sb1: SlowBean() \n" + " $sb2: SlowBean( id > $sb1.id ) \n" + "then " + " System.out.println($sb2 + \" > \"+ $sb1);" + "end\n"; final KieSession ksession = new KieHelper().addContent(drl, ResourceType.DRL) .build() .newKieSession(); final int BEAN_NR = 4; for (int step = 0; step < 2 ; step++) { FactHandle[] fhs = new FactHandle[BEAN_NR]; for (int i = 0; i < BEAN_NR; i++) { fhs[i] = ksession.insert(new SlowBean(i + step * BEAN_NR)); } final CyclicBarrier barrier = new CyclicBarrier(2); new Thread(new Runnable() { @Override public void run() { ksession.fireAllRules(); try { barrier.await(); } catch (Exception e) { throw new RuntimeException(e); } } }).start(); try { Thread.sleep(15L); } catch (InterruptedException e) { throw new RuntimeException(e); } for (int i = 0; i < BEAN_NR; i++) { if (i % 2 == 1) ksession.delete(fhs[i]); } try { barrier.await(); } catch (Exception e) { throw new RuntimeException(e); } System.out.println("Done step " + step); } } public class SlowBean { private final int id; public SlowBean(int id) { this.id = id; } public int getId() { try { Thread.sleep(10L); } catch (InterruptedException e) { throw new RuntimeException(e); } return id; } @Override public String toString() { return "" + id; } } @Test(timeout = 10000) public void testConcurrentFireAndDispose() throws InterruptedException { // DROOLS-1103 String drl = "rule R no-loop timer( int: 1s )\n" + "when\n" + " String()\n" + "then\n" + "end"; KieHelper helper = new KieHelper(); helper.addContent( drl, ResourceType.DRL ); KieBase kbase = helper.build( EventProcessingOption.STREAM ); KieSessionConfiguration ksconf = KieServices.Factory.get().newKieSessionConfiguration(); ksconf.setOption( TimedRuleExecutionOption.YES ); final KieSession ksession = kbase.newKieSession(ksconf, null); Thread t1 = new Thread() { @Override public void run() { LOG.info("before: sleep, dispose()."); try { Thread.sleep(100); } catch (InterruptedException _e) { } LOG.info("before: dispose()."); ksession.dispose(); LOG.info("after: dispose()."); } }; t1.setDaemon(true); t1.start(); try { int i = 0; LOG.info("before: while."); while (true) { ksession.insert("" + i++); ksession.fireAllRules(); } } catch (IllegalStateException e) { // java.lang.IllegalStateException: Illegal method call. This session was previously disposed. // ignore and exit LOG.info("after: while."); } catch (java.util.concurrent.RejectedExecutionException e) { e.printStackTrace(); fail( "java.util.concurrent.RejectedExecutionException should not happen" ); } LOG.info("last line of test."); } @Test(timeout = 10000) public void testFireUntilHaltAndDispose() throws InterruptedException { // DROOLS-1103 String drl = "rule R no-loop timer( int: 1s )\n" + "when\n" + " String()\n" + "then\n" + "end"; KieHelper helper = new KieHelper(); helper.addContent( drl, ResourceType.DRL ); KieBase kbase = helper.build( EventProcessingOption.STREAM ); KieSessionConfiguration ksconf = KieServices.Factory.get().newKieSessionConfiguration(); ksconf.setOption( TimedRuleExecutionOption.YES ); final KieSession ksession = kbase.newKieSession(ksconf, null); new Thread() { @Override public void run() { ksession.fireUntilHalt(); } }.start(); try { Thread.sleep(100); } catch (InterruptedException e) { // do nothing } ksession.insert("xxx"); try { Thread.sleep(100); } catch (InterruptedException e) { // do nothing } ksession.dispose(); } }