[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