[jboss-svn-commits] JBL Code SVN: r25802 - in labs/jbossrules/trunk/drools-core/src: test/java/org/drools/reteoo and 1 other directory.
jboss-svn-commits at lists.jboss.org
jboss-svn-commits at lists.jboss.org
Tue Mar 24 17:27:56 EDT 2009
Author: stampy88
Date: 2009-03-24 17:27:56 -0400 (Tue, 24 Mar 2009)
New Revision: 25802
Added:
labs/jbossrules/trunk/drools-core/src/main/java/org/drools/event/AbstractEventSupport.java
labs/jbossrules/trunk/drools-core/src/test/java/org/drools/reteoo/ReteooRuleBaseMultiThreadedTest.java
Modified:
labs/jbossrules/trunk/drools-core/src/main/java/org/drools/event/RuleBaseEventSupport.java
Log:
Fixed JBRULES-2028
Added: labs/jbossrules/trunk/drools-core/src/main/java/org/drools/event/AbstractEventSupport.java
===================================================================
--- labs/jbossrules/trunk/drools-core/src/main/java/org/drools/event/AbstractEventSupport.java (rev 0)
+++ labs/jbossrules/trunk/drools-core/src/main/java/org/drools/event/AbstractEventSupport.java 2009-03-24 21:27:56 UTC (rev 25802)
@@ -0,0 +1,89 @@
+package org.drools.event;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.Collections;
+import java.util.EventListener;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Base class for Thread-safe Event Support in Drools. Note that subclasses wishing to access
+ * the listeners should do so via the <method>getEventListenersIterator</method> method. This
+ * will provide an Iterator accessing the current snapshot of the underlying list, freeing the
+ * subclasss of thread problems.
+ * <p/>
+ * Please note that for lists of small sizes, and few modifications, the CopyOnWriteArrayList
+ * provides best performance. If the list is modified more often, than a simple ArrayList
+ * with synchonized operations, and copying of the array for iteration is faster.
+ *
+ * @author <a href="mailto:stampy88 at yahoo.com">dave sinclair</a>
+ */
+public abstract class AbstractEventSupport<E extends EventListener> implements Externalizable {
+
+ private static final long serialVersionUID = 400L;
+
+ private List<E> listeners = new CopyOnWriteArrayList<E>();
+
+ @SuppressWarnings("unchecked")
+ public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+ listeners = (List<E>) in.readObject();
+ }
+
+ public void writeExternal(ObjectOutput out) throws IOException {
+ out.writeObject(listeners);
+ }
+
+ protected final Iterator<E> getEventListenersIterator() {
+ return listeners.iterator();
+ }
+
+ /**
+ * Adds the specified listener to the list of listeners. Note that this method needs to be
+ * synchonized because it performs two independent operations on the underlying list
+ *
+ * @param listener to add
+ */
+ public final synchronized void addEventListener(final E listener) {
+ if (!this.listeners.contains(listener)) {
+ this.listeners.add(listener);
+ }
+ }
+
+ /**
+ * Removes all event listeners of the specified class. Note that this method needs to be
+ * synchonized because it performs two independent operations on the underlying list
+ *
+ * @param cls class of listener to remove
+ */
+ public final synchronized void removeEventListener(final Class cls) {
+ for (int listenerIndex = 0; listenerIndex < this.listeners.size();) {
+ E listener = this.listeners.get(listenerIndex);
+
+ if (cls.isAssignableFrom(listener.getClass())) {
+ this.listeners.remove(listenerIndex);
+ } else {
+ listenerIndex++;
+ }
+ }
+ }
+
+ public final void removeEventListener(final E listener) {
+ this.listeners.remove(listener);
+ }
+
+ public List<E> getEventListeners() {
+ return Collections.unmodifiableList(this.listeners);
+ }
+
+ public final int size() {
+ return this.listeners.size();
+ }
+
+ public boolean isEmpty() {
+ return this.listeners.isEmpty();
+ }
+}
Modified: labs/jbossrules/trunk/drools-core/src/main/java/org/drools/event/RuleBaseEventSupport.java
===================================================================
--- labs/jbossrules/trunk/drools-core/src/main/java/org/drools/event/RuleBaseEventSupport.java 2009-03-24 17:26:23 UTC (rev 25801)
+++ labs/jbossrules/trunk/drools-core/src/main/java/org/drools/event/RuleBaseEventSupport.java 2009-03-24 21:27:56 UTC (rev 25802)
@@ -15,273 +15,161 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import java.io.Externalizable;
-import java.io.IOException;
-import java.io.ObjectInput;
-import java.io.ObjectOutput;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
import org.drools.RuleBase;
import org.drools.rule.Package;
import org.drools.rule.Rule;
+import java.util.Iterator;
+
/**
+ * Please note that any event notification methods, e.g. <method>fireBeforePackageAdded</method>, etc.,
+ * always create the event and iterator regardless if there are listeners. This is because if the
+ * check is to see if there are listeners via the <method>isEmpty</method> method, theoretically
+ * there should be synchonrization involved to ensure the <method>isEmpty</method> and
+ * </method>getEventListenersIterator</method> both see the same list contents.
*
* @author etirelli
+ * @author <a href="mailto:stampy88 at yahoo.com">dave sinclair</a>
*/
-public class RuleBaseEventSupport
- implements
- Externalizable {
- /**
- *
- */
- private static final long serialVersionUID = 400L;
- private List<RuleBaseEventListener> listeners = new CopyOnWriteArrayList<RuleBaseEventListener>();
- private transient RuleBase ruleBase;
+public class RuleBaseEventSupport extends AbstractEventSupport<RuleBaseEventListener> {
+ private transient RuleBase ruleBase;
public RuleBaseEventSupport() {
}
+
public RuleBaseEventSupport(final RuleBase ruleBase) {
this.ruleBase = ruleBase;
}
-
- public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
- listeners = (List<RuleBaseEventListener>)in.readObject();
- }
-
- public void writeExternal(ObjectOutput out) throws IOException {
- out.writeObject(listeners);
- }
-
- public void addEventListener(final RuleBaseEventListener listener) {
- if ( !this.listeners.contains( listener ) ) {
- this.listeners.add( listener );
- }
- }
-
+
public void setRuleBase(RuleBase ruleBase) {
this.ruleBase = ruleBase;
}
- public void removeEventListener(Class cls) {
- for ( int i = 0; i < this.listeners.size(); ) {
- RuleBaseEventListener listener = this.listeners.get( i );
- if ( cls.isAssignableFrom( listener.getClass() ) ) {
- this.listeners.remove( i );
- } else {
- i++;
- }
- }
- }
-
- public void removeEventListener(final RuleBaseEventListener listener) {
- this.listeners.remove( listener );
- }
-
- public List<RuleBaseEventListener> getEventListeners() {
- return Collections.unmodifiableList( this.listeners );
- }
-
- public int size() {
- return this.listeners.size();
- }
-
- public boolean isEmpty() {
- return this.listeners.isEmpty();
- }
-
public void fireBeforePackageAdded(final Package newPkg) {
- if ( this.listeners.isEmpty() ) {
- return;
- }
+ final BeforePackageAddedEvent event = new BeforePackageAddedEvent(this.ruleBase, newPkg);
+ final Iterator<RuleBaseEventListener> iter = getEventListenersIterator();
- final BeforePackageAddedEvent event = new BeforePackageAddedEvent( this.ruleBase,
- newPkg );
-
- for ( int i = 0, size = this.listeners.size(); i < size; i++ ) {
- ((RuleBaseEventListener) this.listeners.get( i )).beforePackageAdded( event );
+ while (iter.hasNext()) {
+ iter.next().beforePackageAdded(event);
}
}
public void fireAfterPackageAdded(final Package newPkg) {
- if ( this.listeners.isEmpty() ) {
- return;
- }
+ final AfterPackageAddedEvent event = new AfterPackageAddedEvent(this.ruleBase, newPkg);
+ final Iterator<RuleBaseEventListener> iter = getEventListenersIterator();
- final AfterPackageAddedEvent event = new AfterPackageAddedEvent( this.ruleBase,
- newPkg );
-
- for ( int i = 0, size = this.listeners.size(); i < size; i++ ) {
- ((RuleBaseEventListener) this.listeners.get( i )).afterPackageAdded( event );
+ while (iter.hasNext()) {
+ iter.next().afterPackageAdded(event);
}
}
public void fireBeforePackageRemoved(final Package pkg) {
- if ( this.listeners.isEmpty() ) {
- return;
- }
+ final BeforePackageRemovedEvent event = new BeforePackageRemovedEvent(this.ruleBase, pkg);
+ final Iterator<RuleBaseEventListener> iter = getEventListenersIterator();
- final BeforePackageRemovedEvent event = new BeforePackageRemovedEvent( this.ruleBase,
- pkg );
-
- for ( int i = 0, size = this.listeners.size(); i < size; i++ ) {
- ((RuleBaseEventListener) this.listeners.get( i )).beforePackageRemoved( event );
+ while (iter.hasNext()) {
+ iter.next().beforePackageRemoved(event);
}
}
public void fireAfterPackageRemoved(final Package pkg) {
- if ( this.listeners.isEmpty() ) {
- return;
- }
+ final AfterPackageRemovedEvent event = new AfterPackageRemovedEvent(this.ruleBase, pkg);
+ final Iterator<RuleBaseEventListener> iter = getEventListenersIterator();
- final AfterPackageRemovedEvent event = new AfterPackageRemovedEvent( this.ruleBase,
- pkg );
-
- for ( int i = 0, size = this.listeners.size(); i < size; i++ ) {
- ((RuleBaseEventListener) this.listeners.get( i )).afterPackageRemoved( event );
+ while (iter.hasNext()) {
+ iter.next().afterPackageRemoved(event);
}
}
- //--
public void fireBeforeRuleBaseLocked() {
- if ( this.listeners.isEmpty() ) {
- return;
- }
+ final BeforeRuleBaseLockedEvent event = new BeforeRuleBaseLockedEvent(this.ruleBase);
+ final Iterator<RuleBaseEventListener> iter = getEventListenersIterator();
- final BeforeRuleBaseLockedEvent event = new BeforeRuleBaseLockedEvent( this.ruleBase );
-
- for ( int i = 0, size = this.listeners.size(); i < size; i++ ) {
- ((RuleBaseEventListener) this.listeners.get( i )).beforeRuleBaseLocked( event );
+ while (iter.hasNext()) {
+ iter.next().beforeRuleBaseLocked(event);
}
}
public void fireAfterRuleBaseLocked() {
- if ( this.listeners.isEmpty() ) {
- return;
- }
+ final AfterRuleBaseLockedEvent event = new AfterRuleBaseLockedEvent(this.ruleBase);
+ final Iterator<RuleBaseEventListener> iter = getEventListenersIterator();
- final AfterRuleBaseLockedEvent event = new AfterRuleBaseLockedEvent( this.ruleBase );
-
- for ( int i = 0, size = this.listeners.size(); i < size; i++ ) {
- ((RuleBaseEventListener) this.listeners.get( i )).afterRuleBaseLocked( event );
+ while (iter.hasNext()) {
+ iter.next().afterRuleBaseLocked(event);
}
}
public void fireBeforeRuleBaseUnlocked() {
- if ( this.listeners.isEmpty() ) {
- return;
- }
+ final BeforeRuleBaseUnlockedEvent event = new BeforeRuleBaseUnlockedEvent(this.ruleBase);
+ final Iterator<RuleBaseEventListener> iter = getEventListenersIterator();
- final BeforeRuleBaseUnlockedEvent event = new BeforeRuleBaseUnlockedEvent( this.ruleBase );
-
- for ( int i = 0, size = this.listeners.size(); i < size; i++ ) {
- ((RuleBaseEventListener) this.listeners.get( i )).beforeRuleBaseUnlocked( event );
+ while (iter.hasNext()) {
+ iter.next().beforeRuleBaseUnlocked(event);
}
}
public void fireAfterRuleBaseUnlocked() {
- if ( this.listeners.isEmpty() ) {
- return;
- }
+ final AfterRuleBaseUnlockedEvent event = new AfterRuleBaseUnlockedEvent(this.ruleBase);
+ final Iterator<RuleBaseEventListener> iter = getEventListenersIterator();
- final AfterRuleBaseUnlockedEvent event = new AfterRuleBaseUnlockedEvent( this.ruleBase );
-
- for ( int i = 0, size = this.listeners.size(); i < size; i++ ) {
- ((RuleBaseEventListener) this.listeners.get( i )).afterRuleBaseUnlocked( event );
+ while (iter.hasNext()) {
+ iter.next().afterRuleBaseUnlocked(event);
}
}
- public void fireBeforeRuleAdded(final Package newPkg,
- final Rule rule) {
- if ( this.listeners.isEmpty() ) {
- return;
- }
+ public void fireBeforeRuleAdded(final Package newPkg, final Rule rule) {
+ final BeforeRuleAddedEvent event = new BeforeRuleAddedEvent(this.ruleBase, newPkg, rule);
+ final Iterator<RuleBaseEventListener> iter = getEventListenersIterator();
- final BeforeRuleAddedEvent event = new BeforeRuleAddedEvent( this.ruleBase,
- newPkg,
- rule );
-
- for ( int i = 0, size = this.listeners.size(); i < size; i++ ) {
- ((RuleBaseEventListener) this.listeners.get( i )).beforeRuleAdded( event );
+ while (iter.hasNext()) {
+ iter.next().beforeRuleAdded(event);
}
}
- public void fireAfterRuleAdded(final Package newPkg,
- final Rule rule) {
- if ( this.listeners.isEmpty() ) {
- return;
- }
+ public void fireAfterRuleAdded(final Package newPkg, final Rule rule) {
+ final AfterRuleAddedEvent event = new AfterRuleAddedEvent(this.ruleBase, newPkg, rule);
+ final Iterator<RuleBaseEventListener> iter = getEventListenersIterator();
- final AfterRuleAddedEvent event = new AfterRuleAddedEvent( this.ruleBase,
- newPkg,
- rule );
-
- for ( int i = 0, size = this.listeners.size(); i < size; i++ ) {
- ((RuleBaseEventListener) this.listeners.get( i )).afterRuleAdded( event );
+ while (iter.hasNext()) {
+ iter.next().afterRuleAdded(event);
}
}
- public void fireBeforeRuleRemoved(final Package pkg,
- final Rule rule) {
- if ( this.listeners.isEmpty() ) {
- return;
- }
+ public void fireBeforeRuleRemoved(final Package pkg, final Rule rule) {
+ final BeforeRuleRemovedEvent event = new BeforeRuleRemovedEvent(this.ruleBase, pkg, rule);
+ final Iterator<RuleBaseEventListener> iter = getEventListenersIterator();
- final BeforeRuleRemovedEvent event = new BeforeRuleRemovedEvent( this.ruleBase,
- pkg,
- rule );
-
- for ( int i = 0, size = this.listeners.size(); i < size; i++ ) {
- ((RuleBaseEventListener) this.listeners.get( i )).beforeRuleRemoved( event );
+ while (iter.hasNext()) {
+ iter.next().beforeRuleRemoved(event);
}
}
- public void fireAfterRuleRemoved(final Package pkg,
- final Rule rule) {
- if ( this.listeners.isEmpty() ) {
- return;
- }
+ public void fireAfterRuleRemoved(final Package pkg, final Rule rule) {
+ final AfterRuleRemovedEvent event = new AfterRuleRemovedEvent(this.ruleBase, pkg, rule);
+ final Iterator<RuleBaseEventListener> iter = getEventListenersIterator();
- final AfterRuleRemovedEvent event = new AfterRuleRemovedEvent( this.ruleBase,
- pkg,
- rule );
-
- for ( int i = 0, size = this.listeners.size(); i < size; i++ ) {
- ((RuleBaseEventListener) this.listeners.get( i )).afterRuleRemoved( event );
+ while (iter.hasNext()) {
+ iter.next().afterRuleRemoved(event);
}
}
- public void fireBeforeFunctionRemoved(final Package pkg,
- final String function) {
- if ( this.listeners.isEmpty() ) {
- return;
- }
+ public void fireBeforeFunctionRemoved(final Package pkg, final String function) {
+ final BeforeFunctionRemovedEvent event = new BeforeFunctionRemovedEvent(this.ruleBase, pkg, function);
+ final Iterator<RuleBaseEventListener> iter = getEventListenersIterator();
- final BeforeFunctionRemovedEvent event = new BeforeFunctionRemovedEvent( this.ruleBase,
- pkg,
- function );
-
- for ( int i = 0, size = this.listeners.size(); i < size; i++ ) {
- ((RuleBaseEventListener) this.listeners.get( i )).beforeFunctionRemoved( event );
+ while (iter.hasNext()) {
+ iter.next().beforeFunctionRemoved(event);
}
}
- public void fireAfterFunctionRemoved(final Package pkg,
- final String function) {
- if ( this.listeners.isEmpty() ) {
- return;
- }
+ public void fireAfterFunctionRemoved(final Package pkg, final String function) {
+ final AfterFunctionRemovedEvent event = new AfterFunctionRemovedEvent(this.ruleBase, pkg, function);
+ final Iterator<RuleBaseEventListener> iter = getEventListenersIterator();
- final AfterFunctionRemovedEvent event = new AfterFunctionRemovedEvent( this.ruleBase,
- pkg,
- function );
-
- for ( int i = 0, size = this.listeners.size(); i < size; i++ ) {
- ((RuleBaseEventListener) this.listeners.get( i )).afterFunctionRemoved( event );
+ while (iter.hasNext()) {
+ iter.next().afterFunctionRemoved(event);
}
}
-
}
\ No newline at end of file
Added: labs/jbossrules/trunk/drools-core/src/test/java/org/drools/reteoo/ReteooRuleBaseMultiThreadedTest.java
===================================================================
--- labs/jbossrules/trunk/drools-core/src/test/java/org/drools/reteoo/ReteooRuleBaseMultiThreadedTest.java (rev 0)
+++ labs/jbossrules/trunk/drools-core/src/test/java/org/drools/reteoo/ReteooRuleBaseMultiThreadedTest.java 2009-03-24 21:27:56 UTC (rev 25802)
@@ -0,0 +1,169 @@
+package org.drools.reteoo;
+
+import junit.framework.Assert;
+import org.drools.DroolsTestCase;
+import org.drools.RuleBaseFactory;
+import org.drools.StatefulSession;
+import org.drools.WorkingMemory;
+import org.drools.base.ClassFieldAccessorCache;
+import org.drools.rule.JavaDialectRuntimeData;
+import org.drools.rule.Rule;
+import org.drools.spi.Consequence;
+import org.drools.spi.KnowledgeHelper;
+
+/**
+ * Test case to ensure that the ReteooRuleBase is thread safe. Specifically to test for
+ * deadlocks when modifying the rulebase while creating new sessions.
+ *
+ * @author <a href="mailto:stampy88 at yahoo.com">dave sinclair</a>
+ */
+public class ReteooRuleBaseMultiThreadedTest extends DroolsTestCase {
+
+ ReteooRuleBase ruleBase;
+ Rule rule;
+ org.drools.rule.Package pkg;
+
+ public void setUp() {
+ this.ruleBase = (ReteooRuleBase) RuleBaseFactory.newRuleBase();
+
+ pkg = new org.drools.rule.Package("org.droos.test");
+ pkg.setClassFieldAccessorCache(new ClassFieldAccessorCache(Thread.currentThread().getContextClassLoader()));
+
+ JavaDialectRuntimeData data = new JavaDialectRuntimeData();
+ data.onAdd(pkg.getDialectRuntimeRegistry(), ruleBase.getRootClassLoader());
+ pkg.getDialectRuntimeRegistry().setDialectData("java", data);
+
+ // we need to add one rule to the package because the previous deadlock was encountered
+ // while removing rules from a package when said package is removed from the rulebase
+ rule = new Rule("Test");
+ rule.setDialect("java");
+ rule.setConsequence(new Consequence() {
+ public void evaluate(KnowledgeHelper knowledgeHelper, WorkingMemory workingMemory) throws Exception {
+
+ }
+ });
+ pkg.addRule(rule);
+
+ ruleBase.addPackage(pkg);
+ }
+
+ public void testNewSessionWhileModifyingRuleBase() throws InterruptedException {
+ PackageModifier modifier = new PackageModifier();
+ SessionCreator creator = new SessionCreator();
+
+ creator.start();
+ modifier.start();
+
+ // 10 seconds should be more than enough time to see if the modifer and creator
+ // get deadlocked
+ Thread.sleep(10000);
+
+ boolean deadlockDetected = creator.isBlocked() && modifier.isBlocked();
+
+ if (deadlockDetected) {
+ // dump both stacks to show it
+ printThreadStatus(creator);
+ printThreadStatus(modifier);
+ }
+
+ Assert.assertEquals("Threads are deadlocked! See previous stacks for more detail", false, deadlockDetected);
+
+ // check to see if either had an exception also
+ if (creator.isInError()) {
+ creator.getError().printStackTrace();
+ }
+ Assert.assertEquals("Exception in creator thread", false, creator.isInError());
+
+ if (modifier.isInError()) {
+ modifier.getError().printStackTrace();
+ }
+ Assert.assertEquals("Exception in modifier thread", false, modifier.isInError());
+ }
+
+ private void printThreadStatus(Thread thread) {
+ StackTraceElement[] frames = thread.getStackTrace();
+
+ System.err.println(thread.getName() + ": " + thread.getState());
+
+ for (StackTraceElement frame : frames) {
+ System.err.println(frame);
+ }
+
+ System.err.println();
+ }
+
+ private abstract class BlockedThread extends Thread {
+ private static final int NUMER_ATTEMPTS = 50000;
+ private volatile Throwable error;
+
+ BlockedThread(String name) {
+ super(name);
+ setDaemon(true);
+ }
+
+ public boolean isInError() {
+ return error != null;
+ }
+
+ public Throwable getError() {
+ return error;
+ }
+
+ public boolean isBlocked() {
+ return getState() == State.BLOCKED;
+ }
+
+ public void run() {
+ int numAttempts = 0;
+
+ try {
+ while (numAttempts < NUMER_ATTEMPTS) {
+ doOperation();
+
+ numAttempts++;
+ }
+ } catch (Throwable t) {
+ error = t;
+ }
+ }
+
+ abstract void doOperation();
+ }
+
+ /**
+ * This thread will continually try to remove a package and add a package to
+ * the rulebase
+ */
+ private class PackageModifier extends BlockedThread {
+ private PackageModifier() {
+ super("Rulebase Modifier Thread");
+ }
+
+ void doOperation() {
+ ruleBase.removePackage(pkg.getName());
+ ruleBase.addPackage(pkg);
+ }
+ }
+
+ /**
+ * This thread will continually create and dispose new stateful sessions
+ */
+ private class SessionCreator extends BlockedThread {
+
+ private SessionCreator() {
+ super("Session Creator Thread");
+ }
+
+ void doOperation() {
+ StatefulSession session = null;
+
+ try {
+ session = ruleBase.newStatefulSession();
+ } finally {
+ if (session != null) {
+ session.dispose();
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
More information about the jboss-svn-commits
mailing list