Quantcast

groovy git commit: GROOVY-8067: Possible deadlock when creating new ClassInfo entries in the cache (closes #489)

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

groovy git commit: GROOVY-8067: Possible deadlock when creating new ClassInfo entries in the cache (closes #489)

jwagenleitner-2
Repository: groovy
Updated Branches:
  refs/heads/master 75ceda4b2 -> beab89aff


GROOVY-8067: Possible deadlock when creating new ClassInfo entries in the cache (closes #489)


Project: http://git-wip-us.apache.org/repos/asf/groovy/repo
Commit: http://git-wip-us.apache.org/repos/asf/groovy/commit/beab89af
Tree: http://git-wip-us.apache.org/repos/asf/groovy/tree/beab89af
Diff: http://git-wip-us.apache.org/repos/asf/groovy/diff/beab89af

Branch: refs/heads/master
Commit: beab89aff9c5faf621603bcbe92f8c9b4139f446
Parents: 75ceda4
Author: John Wagenleitner <[hidden email]>
Authored: Sat Feb 11 08:17:44 2017 -0800
Committer: John Wagenleitner <[hidden email]>
Committed: Sat Feb 11 08:17:44 2017 -0800

----------------------------------------------------------------------
 settings.gradle                                 |   3 +-
 .../codehaus/groovy/reflection/ClassInfo.java   |  55 +++---
 .../metaclass/MetaClassRegistryImpl.java        |  12 +-
 .../util/ManagedConcurrentLinkedQueue.java      | 180 +++++++++++++++++++
 .../codehaus/groovy/util/ManagedLinkedList.java |   2 +
 .../ManagedConcurrentLinkedQueueTest.groovy     |  88 +++++++++
 subprojects/stress/README.adoc                  |  19 ++
 subprojects/stress/build.gradle                 |  30 ++++
 .../org/apache/groovy/stress/util/GCUtils.java  |  39 ++++
 .../apache/groovy/stress/util/ThreadUtils.java  |  43 +++++
 .../reflection/ClassInfoDeadlockStressTest.java | 138 ++++++++++++++
 .../ManagedConcurrentLinkedQueueStressTest.java | 164 +++++++++++++++++
 12 files changed, 731 insertions(+), 42 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/groovy/blob/beab89af/settings.gradle
----------------------------------------------------------------------
diff --git a/settings.gradle b/settings.gradle
index 7d1ab24..5c1bff7 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -33,7 +33,8 @@ def subprojects = ['groovy-ant',
         'groovy-test',
         'groovy-testng',
         'groovy-xml',
-        'groovy-macro'
+        'groovy-macro',
+        'stress'
 ]
 
 if (JavaVersion.current().isJava8Compatible()) {

http://git-wip-us.apache.org/repos/asf/groovy/blob/beab89af/src/main/org/codehaus/groovy/reflection/ClassInfo.java
----------------------------------------------------------------------
diff --git a/src/main/org/codehaus/groovy/reflection/ClassInfo.java b/src/main/org/codehaus/groovy/reflection/ClassInfo.java
index b240846..93d7b6b 100644
--- a/src/main/org/codehaus/groovy/reflection/ClassInfo.java
+++ b/src/main/org/codehaus/groovy/reflection/ClassInfo.java
@@ -74,7 +74,8 @@ public class ClassInfo implements Finalizable {
     private static final ReferenceBundle softBundle = ReferenceBundle.getSoftBundle();
     private static final ReferenceBundle weakBundle = ReferenceBundle.getWeakBundle();
     
-    private static final ManagedLinkedList<ClassInfo> modifiedExpandos = new ManagedLinkedList<ClassInfo>(weakBundle);
+    private static final ManagedConcurrentLinkedQueue<ClassInfo> modifiedExpandos =
+            new ManagedConcurrentLinkedQueue<ClassInfo>(weakBundle);
 
     private static final GroovyClassValue<ClassInfo> globalClassValue = GroovyClassValueFactory.createGroovyClassValue(new ComputeValue<ClassInfo>(){
  @Override
@@ -110,13 +111,11 @@ public class ClassInfo implements Finalizable {
     }
 
     public static void clearModifiedExpandos() {
-        synchronized(modifiedExpandos){
-        for (Iterator<ClassInfo> it = modifiedExpandos.iterator(); it.hasNext(); ) {
-            ClassInfo info = it.next();
-            it.remove();
-            info.setStrongMetaClass(null);
-        }
-    }
+        for (Iterator<ClassInfo> itr = modifiedExpandos.iterator(); itr.hasNext(); ) {
+            ClassInfo info = itr.next();
+            itr.remove();
+            info.setStrongMetaClass(null);
+        }
     }
 
     /**
@@ -186,30 +185,20 @@ public class ClassInfo implements Finalizable {
         MetaClass strongRef = strongMetaClass;
         
         if (strongRef instanceof ExpandoMetaClass) {
-          ((ExpandoMetaClass)strongRef).inRegistry = false;
-          synchronized(modifiedExpandos){
-            for (Iterator<ClassInfo> it = modifiedExpandos.iterator(); it.hasNext(); ) {
-              ClassInfo info = it.next();
-              if(info == this){
-                it.remove();
-              }
+            ((ExpandoMetaClass)strongRef).inRegistry = false;
+            for (Iterator<ClassInfo> itr = modifiedExpandos.iterator(); itr.hasNext(); ) {
+                ClassInfo info = itr.next();
+                if(info == this) {
+                    itr.remove();
+                }
             }
-          }
         }
 
         strongMetaClass = answer;
 
         if (answer instanceof ExpandoMetaClass) {
-          ((ExpandoMetaClass)answer).inRegistry = true;
-          synchronized(modifiedExpandos){
-            for (Iterator<ClassInfo> it = modifiedExpandos.iterator(); it.hasNext(); ) {
-              ClassInfo info = it.next();
-                if(info == this){
-                  it.remove();
-                }
-             }
-             modifiedExpandos.add(this);
-          }
+            ((ExpandoMetaClass)answer).inRegistry = true;
+            modifiedExpandos.add(this);
         }
 
         replaceWeakMetaClassRef(null);
@@ -457,26 +446,22 @@ public class ClassInfo implements Finalizable {
 
     private static class GlobalClassSet {
     
-     private final ManagedLinkedList<ClassInfo> items = new ManagedLinkedList<ClassInfo>(weakBundle);
+     private final ManagedConcurrentLinkedQueue<ClassInfo> items = new ManagedConcurrentLinkedQueue<ClassInfo>(weakBundle);
     
      public int size(){
- return values().size();
+    return values().size();
      }
     
      public int fullSize(){
- return values().size();
+    return values().size();
      }
     
      public Collection<ClassInfo> values(){
-     synchronized(items){
-     return Arrays.asList(items.toArray(new ClassInfo[0]));
-     }
+        return items.values();
      }
     
      public void add(ClassInfo value){
-     synchronized(items){
-     items.add(value);
-     }
+            items.add(value);
      }
 
     }

http://git-wip-us.apache.org/repos/asf/groovy/blob/beab89af/src/main/org/codehaus/groovy/runtime/metaclass/MetaClassRegistryImpl.java
----------------------------------------------------------------------
diff --git a/src/main/org/codehaus/groovy/runtime/metaclass/MetaClassRegistryImpl.java b/src/main/org/codehaus/groovy/runtime/metaclass/MetaClassRegistryImpl.java
index 09ba116..c8692a0 100644
--- a/src/main/org/codehaus/groovy/runtime/metaclass/MetaClassRegistryImpl.java
+++ b/src/main/org/codehaus/groovy/runtime/metaclass/MetaClassRegistryImpl.java
@@ -26,9 +26,9 @@ import org.codehaus.groovy.runtime.*;
 import org.codehaus.groovy.runtime.m12n.ExtensionModule;
 import org.codehaus.groovy.runtime.m12n.ExtensionModuleRegistry;
 import org.codehaus.groovy.runtime.m12n.ExtensionModuleScanner;
+import org.codehaus.groovy.util.ManagedConcurrentLinkedQueue;
 import org.codehaus.groovy.vmplugin.VMPluginFactory;
 import org.codehaus.groovy.util.FastArray;
-import org.codehaus.groovy.util.ManagedLinkedList;
 import org.codehaus.groovy.util.ReferenceBundle;
 
 import java.lang.reflect.Constructor;
@@ -62,7 +62,7 @@ public class MetaClassRegistryImpl implements MetaClassRegistry{
 
     private final LinkedList<MetaClassRegistryChangeEventListener> changeListenerList = new LinkedList<MetaClassRegistryChangeEventListener>();
     private final LinkedList<MetaClassRegistryChangeEventListener> nonRemoveableChangeListenerList = new LinkedList<MetaClassRegistryChangeEventListener>();
-    private final ManagedLinkedList metaClassInfo = new ManagedLinkedList<MetaClass>(ReferenceBundle.getWeakBundle());
+    private final ManagedConcurrentLinkedQueue<MetaClass> metaClassInfo = new ManagedConcurrentLinkedQueue<MetaClass>(ReferenceBundle.getWeakBundle());
     private final ExtensionModuleRegistry moduleRegistry = new ExtensionModuleRegistry();
 
     public static final int LOAD_DEFAULT = 0;
@@ -125,6 +125,9 @@ public class MetaClassRegistryImpl implements MetaClassRegistry{
 
         addNonRemovableMetaClassRegistryChangeEventListener(new MetaClassRegistryChangeEventListener(){
             public void updateConstantMetaClass(MetaClassRegistryChangeEvent cmcu) {
+                // The calls to DefaultMetaClassInfo.setPrimitiveMeta and sdyn.setBoolean need to be
+                // ordered. Even though metaClassInfo is thread-safe, it is included in the block
+                // so the meta classes are added to the queue in the same order.
                 synchronized (metaClassInfo) {
                    metaClassInfo.add(cmcu.getNewMetaClass());
                    DefaultMetaClassInfo.getNewConstantMetaClassVersioning();
@@ -447,10 +450,7 @@ public class MetaClassRegistryImpl implements MetaClassRegistry{
      * @return the iterator.
      */    
     public Iterator iterator() {
-        final MetaClass[] refs;
-        synchronized (metaClassInfo) {
-            refs = (MetaClass[]) metaClassInfo.toArray(new MetaClass[0]);
-        }
+        final MetaClass[] refs = metaClassInfo.toArray(new MetaClass[0]);
         
         return new Iterator() {
             // index in the ref array

http://git-wip-us.apache.org/repos/asf/groovy/blob/beab89af/src/main/org/codehaus/groovy/util/ManagedConcurrentLinkedQueue.java
----------------------------------------------------------------------
diff --git a/src/main/org/codehaus/groovy/util/ManagedConcurrentLinkedQueue.java b/src/main/org/codehaus/groovy/util/ManagedConcurrentLinkedQueue.java
new file mode 100644
index 0000000..ab98f72
--- /dev/null
+++ b/src/main/org/codehaus/groovy/util/ManagedConcurrentLinkedQueue.java
@@ -0,0 +1,180 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.codehaus.groovy.util;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+/**
+ * A queue that stores values wrapped in a Reference, the type of which is
+ * determined by the provided {@link ReferenceBundle}. References stored
+ * in this queue will be removed when reference processing occurs.
+ * <p>
+ * This queue is backed by a {@link ConcurrentLinkedQueue} and is thread safe.
+ * The iterator will only return non-null values (reachable) and is based on
+ * the "weakly consistent" iterator of the underlying {@link ConcurrentLinkedQueue}.
+ *
+ * @param <T> the type of values to store
+ */
+public class ManagedConcurrentLinkedQueue<T> implements Iterable<T> {
+
+    private final ReferenceBundle bundle;
+    private final ConcurrentLinkedQueue<Element<T>> queue;
+
+    /**
+     * Creates an empty ManagedConcurrentLinkedQueue that will use the provided
+     * {@code ReferenceBundle} to store values as the given Reference
+     * type.
+     *
+     * @param bundle used to create the appropriate Reference type
+     *               for the values stored
+     */
+    public ManagedConcurrentLinkedQueue(ReferenceBundle bundle) {
+        this.bundle = bundle;
+        this.queue = new ConcurrentLinkedQueue<Element<T>>();
+    }
+
+    /**
+     * Adds the specified value to the queue.
+     *
+     * @param value the value to add
+     */
+    public void add(T value) {
+        Element<T> e = new Element<T>(value);
+        queue.offer(e);
+    }
+
+    /**
+     * Returns {@code true} if this queue contains no elements.
+     * <p>
+     * This method does not check the elements to verify they contain
+     * non-null reference values.
+     */
+    public boolean isEmpty() {
+        return queue.isEmpty();
+    }
+
+    /**
+     * Returns an array containing all values from this queue in the sequence they
+     * were added.
+     *
+     * @param tArray the array to populate if big enough, else a new array with
+     *               the same runtime type
+     * @return an array containing all non-null values in this queue
+     */
+    public T[] toArray(T[] tArray) {
+        return values().toArray(tArray);
+    }
+
+    /**
+     * Returns a list containing all values from this queue in the
+     * sequence they were added.
+     */
+    public List<T> values() {
+        List<T> result = new ArrayList<T>();
+        for (Iterator<T> itr = iterator(); itr.hasNext(); ) {
+            result.add(itr.next());
+        }
+        return result;
+    }
+
+    /**
+     * Returns an iterator over all non-null values in this queue.  The values should be
+     * returned in the order they were added.
+     */
+    @Override
+    public Iterator<T> iterator() {
+        return new Itr(queue.iterator());
+    }
+
+    private class Element<V> extends ManagedReference<V> {
+
+        Element(V value) {
+            super(bundle, value);
+        }
+
+        @Override
+        public void finalizeReference() {
+            queue.remove(this);
+            super.finalizeReference();
+        }
+
+    }
+
+    private class Itr implements Iterator<T> {
+
+        final Iterator<Element<T>> wrapped;
+
+        T value;
+        Element<T> current;
+        boolean exhausted;
+
+        Itr(Iterator<Element<T>> wrapped) {
+            this.wrapped = wrapped;
+        }
+
+        @Override
+        public boolean hasNext() {
+            if (!exhausted && value == null) {
+                advance();
+            }
+            return value != null;
+        }
+
+        @Override
+        public T next() {
+            if (!hasNext()) {
+                throw new NoSuchElementException();
+            }
+            T next = value;
+            value = null;
+            return next;
+        }
+
+        @Override
+        public void remove() {
+            if (current == null || value != null) {
+                throw new IllegalStateException("Next method has not been called");
+            }
+            wrapped.remove();
+            current = null;
+        }
+
+        private void advance() {
+            while (wrapped.hasNext()) {
+                Element<T> e = wrapped.next();
+                T v = e.get();
+                if (v != null) {
+                    current = e;
+                    value = v;
+                    return;
+                }
+                wrapped.remove();
+            }
+            value = null;
+            current = null;
+            exhausted = true;
+        }
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/beab89af/src/main/org/codehaus/groovy/util/ManagedLinkedList.java
----------------------------------------------------------------------
diff --git a/src/main/org/codehaus/groovy/util/ManagedLinkedList.java b/src/main/org/codehaus/groovy/util/ManagedLinkedList.java
index 05fc911..4b7b37c 100644
--- a/src/main/org/codehaus/groovy/util/ManagedLinkedList.java
+++ b/src/main/org/codehaus/groovy/util/ManagedLinkedList.java
@@ -29,7 +29,9 @@ import java.util.List;
  *
  * @author Jochen Theodorou
  * @since 1.6
+ * @deprecated replaced by {@link ManagedConcurrentLinkedQueue}
  */
+@Deprecated
 public class ManagedLinkedList<T> {
 
     private final class Element<V> extends ManagedReference<V> {

http://git-wip-us.apache.org/repos/asf/groovy/blob/beab89af/src/test/org/codehaus/groovy/util/ManagedConcurrentLinkedQueueTest.groovy
----------------------------------------------------------------------
diff --git a/src/test/org/codehaus/groovy/util/ManagedConcurrentLinkedQueueTest.groovy b/src/test/org/codehaus/groovy/util/ManagedConcurrentLinkedQueueTest.groovy
new file mode 100644
index 0000000..5eecd6d
--- /dev/null
+++ b/src/test/org/codehaus/groovy/util/ManagedConcurrentLinkedQueueTest.groovy
@@ -0,0 +1,88 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.codehaus.groovy.util
+
+class ManagedConcurrentLinkedQueueTest extends GroovyTestCase {
+
+    def queue
+
+    void setUp() {
+        def manager = ReferenceManager.createIdlingManager(null)
+        def bundle = new ReferenceBundle(manager, ReferenceType.HARD)
+        queue = new ManagedConcurrentLinkedQueue(bundle)
+    }
+
+    void testElementAdd() {
+        queue.add(1)
+        def i = 0
+        queue.each {
+            assert it==1
+            i++
+        }
+        assert i ==1
+    }
+
+    void testEmptylist() {
+        assert queue.isEmpty()
+    }
+
+    void testRemoveinTheMiddle() {
+        queue.add(1)
+        queue.add(2)
+        queue.add(3)
+        queue.add(4)
+        queue.add(5)
+        def iter = queue.iterator()
+        while (iter.hasNext()) {
+            if (iter.next()==3) iter.remove()
+        }
+        def val = queue.inject(0){value, it-> value+it}
+        assert val == 12
+    }
+
+    void testAddRemove() {
+        10.times {
+            queue.add(it)
+            def iter = queue.iterator()
+            while (iter.hasNext()) {
+                if (iter.next()==it) iter.remove()
+            }
+        }
+        assert queue.isEmpty()
+    }
+
+    void testIteratorThrowsNoSuchElementException() {
+        shouldFail(NoSuchElementException) {
+            queue.add(1)
+            def iter = queue.iterator()
+            assert iter.next() == 1
+            iter.next()
+        }
+    }
+
+    void testIteratorThrowsOnRemoveIfNextNotCalled() {
+        shouldFail(IllegalStateException) {
+            queue.add(1)
+            def iter = queue.iterator()
+            assert iter.hasNext()
+            iter.remove()
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/beab89af/subprojects/stress/README.adoc
----------------------------------------------------------------------
diff --git a/subprojects/stress/README.adoc b/subprojects/stress/README.adoc
new file mode 100644
index 0000000..23a6895
--- /dev/null
+++ b/subprojects/stress/README.adoc
@@ -0,0 +1,19 @@
+= Stress Tests
+
+Tests in this subproject are used for stress testing.  These types of tests
+will normally involve calls to `System.gc()`, spinning up many threads, and
+may attempt to create OutOfMemory errors.
+
+These tests can be long running and may be prone to failure on different
+platforms, so in order to run these tests you must enable them as follows:
+
+    ./gradlew -PstressTests :stress:test
+
+You can run a single test with the following command:
+
+    ./gradlew -PstressTests :stress:test --tests org.codehaus.groovy.util.SomeTest
+
+Or run all tests under in a given package and subpackages:
+
+    ./gradlew -PstressTests :stress:test --tests org.codehaus.*
+

http://git-wip-us.apache.org/repos/asf/groovy/blob/beab89af/subprojects/stress/build.gradle
----------------------------------------------------------------------
diff --git a/subprojects/stress/build.gradle b/subprojects/stress/build.gradle
new file mode 100644
index 0000000..b58ec83
--- /dev/null
+++ b/subprojects/stress/build.gradle
@@ -0,0 +1,30 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.
+ */
+dependencies {
+    testCompile project(':groovy-test')
+}
+
+test {
+    minHeapSize = '512m'
+    maxHeapSize = '512m'
+    onlyIf {
+        project.hasProperty('stressTests')
+    }
+    outputs.upToDateWhen { false }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/beab89af/subprojects/stress/src/test/java/org/apache/groovy/stress/util/GCUtils.java
----------------------------------------------------------------------
diff --git a/subprojects/stress/src/test/java/org/apache/groovy/stress/util/GCUtils.java b/subprojects/stress/src/test/java/org/apache/groovy/stress/util/GCUtils.java
new file mode 100644
index 0000000..2a43e99
--- /dev/null
+++ b/subprojects/stress/src/test/java/org/apache/groovy/stress/util/GCUtils.java
@@ -0,0 +1,39 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.apache.groovy.stress.util;
+
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+
+public class GCUtils {
+
+    private GCUtils() { }
+
+    public static void gc() {
+        Reference<Object> dummy = new WeakReference<Object>(new Object());
+        System.gc();
+        int max = 0;
+        while (dummy.get() != null && max++ < 10) {
+            System.gc();
+        }
+        if (dummy.get() != null) {
+            throw new Error("GC attempt failed");
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/beab89af/subprojects/stress/src/test/java/org/apache/groovy/stress/util/ThreadUtils.java
----------------------------------------------------------------------
diff --git a/subprojects/stress/src/test/java/org/apache/groovy/stress/util/ThreadUtils.java b/subprojects/stress/src/test/java/org/apache/groovy/stress/util/ThreadUtils.java
new file mode 100644
index 0000000..83df303
--- /dev/null
+++ b/subprojects/stress/src/test/java/org/apache/groovy/stress/util/ThreadUtils.java
@@ -0,0 +1,43 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.apache.groovy.stress.util;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.CyclicBarrier;
+
+public class ThreadUtils {
+
+    private ThreadUtils() { }
+
+    public static void await(CountDownLatch latch) {
+        try {
+            latch.await();
+        } catch (InterruptedException e) {
+            throw new Error(e);
+        }
+    }
+
+    public static void await(CyclicBarrier barrier) {
+        try {
+            barrier.await();
+        } catch (Exception e) {
+            throw new Error(e);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/beab89af/subprojects/stress/src/test/java/org/codehaus/groovy/reflection/ClassInfoDeadlockStressTest.java
----------------------------------------------------------------------
diff --git a/subprojects/stress/src/test/java/org/codehaus/groovy/reflection/ClassInfoDeadlockStressTest.java b/subprojects/stress/src/test/java/org/codehaus/groovy/reflection/ClassInfoDeadlockStressTest.java
new file mode 100644
index 0000000..338e006
--- /dev/null
+++ b/subprojects/stress/src/test/java/org/codehaus/groovy/reflection/ClassInfoDeadlockStressTest.java
@@ -0,0 +1,138 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.codehaus.groovy.reflection;
+
+import groovy.lang.GroovyClassLoader;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.groovy.stress.util.GCUtils;
+import org.apache.groovy.stress.util.ThreadUtils;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ * Tests for deadlocks in the ClassInfo caching.
+ *
+ */
+public class ClassInfoDeadlockStressTest {
+
+    private static final int DEADLOCK_TRIES = 8;
+    private static final int THREAD_COUNT = 8;
+
+    private final CountDownLatch startLatch = new CountDownLatch(1);
+    private final CountDownLatch completeLatch = new CountDownLatch(THREAD_COUNT);
+    private final GroovyClassLoader gcl = new GroovyClassLoader();
+    private final AtomicInteger counter = new AtomicInteger();
+
+    /**
+     * We first generate a large number of ClassInfo instances for classes
+     * that are no longer reachable.  Then queue up threads to all request
+     * ClassInfo instances for new classes simultaneously to ensure that
+     * clearing the old references wont deadlock the creation of new
+     * instances.
+     * <p>
+     * GROOVY-8067
+     */
+    @Test
+    public void testDeadlock() throws Exception {
+        for (int i = 1; i <= DEADLOCK_TRIES; i++) {
+            System.out.println("Test Number: " + i);
+            generateGarbage();
+            GCUtils.gc();
+            attemptDeadlock(null);
+        }
+    }
+
+    @Test
+    public void testRequestsForSameClassInfo() throws Exception {
+        Class<?> newClass = createRandomClass();
+        for (int i = 1; i <= DEADLOCK_TRIES; i++) {
+            System.out.println("Test Number: " + i);
+            generateGarbage();
+            GCUtils.gc();
+            attemptDeadlock(newClass);
+        }
+        ClassInfo newClassInfo = ClassInfo.getClassInfo(newClass);
+        for (ClassInfo ci : ClassInfo.getAllClassInfo()) {
+            if (ci.getTheClass() == newClass && ci != newClassInfo) {
+                fail("Found multiple ClassInfo instances for class");
+            }
+        }
+    }
+
+    private void attemptDeadlock(final Class<?> cls) throws Exception {
+        for (int i = 0; i < THREAD_COUNT; i++) {
+            Runnable runnable = new Runnable() {
+                @Override
+                public void run() {
+                    Class<?> newClass = (cls == null) ? createRandomClass() : cls;
+                    ThreadUtils.await(startLatch);
+                    ClassInfo ci = ClassInfo.getClassInfo(newClass);
+                    assertEquals(newClass, ci.getTheClass());
+                    completeLatch.countDown();
+                }
+            };
+            Thread t = new Thread(runnable);
+            t.setDaemon(true);
+            t.start();
+        }
+        startLatch.countDown();
+        completeLatch.await(10L, TimeUnit.SECONDS);
+        if (completeLatch.getCount() != 0) {
+            System.err.println("Possible deadlock, grab a thread dump now");
+            completeLatch.await(1L, TimeUnit.MINUTES);
+            if (completeLatch.getCount() == 0) {
+                System.out.println("No deadlock, but took longer than expected");
+            } else {
+                fail("Deadlock occurred");
+            }
+        } else {
+            System.out.println("No deadlock detected");
+        }
+    }
+
+    // This may deadlock so run in a separate thread
+    private void generateGarbage() throws Exception {
+        System.out.println("Generating garbage");
+        Runnable runnable = new Runnable() {
+            @Override
+            public void run() {
+                for (int i = 0; i < 5000; i++) {
+                    Class<?> c = createRandomClass();
+                    ClassInfo ci = ClassInfo.getClassInfo(c);
+                }
+            }
+        };
+        Thread t = new Thread(runnable, "GenerateGarbageThread");
+        t.setDaemon(true);
+        t.start();
+        t.join(TimeUnit.SECONDS.toMillis(120L));
+        if (t.isAlive()) {
+            fail("Deadlock detected while generating garbage");
+        }
+    }
+
+    private Class<?> createRandomClass() {
+        return gcl.parseClass("println foo-" + counter.incrementAndGet(), "Script1.groovy");
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/beab89af/subprojects/stress/src/test/java/org/codehaus/groovy/util/ManagedConcurrentLinkedQueueStressTest.java
----------------------------------------------------------------------
diff --git a/subprojects/stress/src/test/java/org/codehaus/groovy/util/ManagedConcurrentLinkedQueueStressTest.java b/subprojects/stress/src/test/java/org/codehaus/groovy/util/ManagedConcurrentLinkedQueueStressTest.java
new file mode 100644
index 0000000..d626f57
--- /dev/null
+++ b/subprojects/stress/src/test/java/org/codehaus/groovy/util/ManagedConcurrentLinkedQueueStressTest.java
@@ -0,0 +1,164 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.codehaus.groovy.util;
+
+import org.apache.groovy.stress.util.GCUtils;
+import org.apache.groovy.stress.util.ThreadUtils;
+import org.junit.*;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.*;
+
+public class ManagedConcurrentLinkedQueueStressTest {
+
+    static final int ENTRY_COUNT = 8196;
+    static final ReferenceBundle bundle = ReferenceBundle.getWeakBundle();
+
+    ManagedConcurrentLinkedQueue<Object> queue = new ManagedConcurrentLinkedQueue<Object>(bundle);
+
+    @Test
+    public void testQueueRemovesCollectedEntries() {
+        // Keep a hardref so we can test get later
+        List<Object> elements = populate();
+        assertEquals("should contain all entries", ENTRY_COUNT, queue.values().size());
+
+        Object o = elements.get(ENTRY_COUNT / 2);
+        assertTrue("should contain an element", queue.values().contains(o));
+        o = null;
+
+        elements.remove(0);
+        GCUtils.gc();
+        assertEquals("should have one less element", ENTRY_COUNT - 1, queue.values().size());
+
+        elements.clear();
+        GCUtils.gc();
+
+        // Add an entries to force ReferenceManager.removeStaleEntries
+        Object last = new Object();
+        queue.add(last);
+        assertEquals("should only contain last added", 1, queue.values().size());
+    }
+
+    @Test
+    public void testQueueRemovesCollectedEntriesOnIteration() {
+        List<Object> elements = populate();
+        assertEquals("should contain all entries", ENTRY_COUNT, queue.values().size());
+        elements.clear();
+        GCUtils.gc();
+        assertFalse("Iterator should remove collected elements", queue.iterator().hasNext());
+    }
+
+    @Test
+    public void testQueueIterationManyThreadsWithRemove() throws Exception {
+        List<Object> elements = populate();
+        assertEquals("should contain all entries", ENTRY_COUNT, queue.values().size());
+        multipleIterateAndRemove(8, ENTRY_COUNT);
+    }
+
+    @Test
+    public void testQueueIterationManyThreadsWithRemoveWithGC() throws Exception {
+        List<Object> elements = populate();
+        assertEquals("should contain all entries", ENTRY_COUNT, queue.values().size());
+        // Remove some refs so GC will work in order to test multiple iterating threads
+        // removing collected references
+        int i = 0;
+        for (Iterator<Object> itr = elements.iterator(); itr.hasNext();) {
+            itr.next();
+            if (i++ % 8 == 0) {
+                itr.remove();
+            }
+        }
+        GCUtils.gc();
+        multipleIterateAndRemove(8, elements.size());
+    }
+
+    @Test
+    public void testQueueRemoveCalledByMultipleThreadsOnSameElement() throws Exception {
+        final Object value1 = new Object();
+        final Object value2 = new Object();
+        queue.add(value1);
+        queue.add(value2);
+        final int threadCount = 8;
+        final CyclicBarrier barrier = new CyclicBarrier(threadCount + 1);
+        for (int i = 0; i < threadCount; i++) {
+            Thread t = new Thread(new Runnable() {
+                @Override
+                public void run() {
+                    Iterator<Object> itr = queue.iterator();
+                    Object o = itr.next();
+                    assertEquals(value1, o);
+                    ThreadUtils.await(barrier);
+                    itr.remove();
+                    ThreadUtils.await(barrier);
+                }
+            });
+            t.setDaemon(true);
+            t.start();
+        }
+        ThreadUtils.await(barrier); // start
+        barrier.await(1L, TimeUnit.MINUTES);
+        Iterator<Object> itr = queue.iterator();
+        assertTrue(itr.hasNext());
+        assertEquals(value2, itr.next());
+        assertFalse(itr.hasNext());
+    }
+
+    private void multipleIterateAndRemove(final int threadCount, final int expectCount) throws Exception {
+        final CyclicBarrier barrier = new CyclicBarrier(threadCount + 1);
+        for (int i = 0; i < threadCount; i++) {
+            final int idx = i;
+            Thread t = new Thread(new Runnable() {
+                @Override
+                public void run() {
+                    int elementCount = 0;
+                    Iterator<Object> itr = queue.iterator();
+                    ThreadUtils.await(barrier);
+                    while (itr.hasNext()) {
+                        itr.next();
+                        if (elementCount++ == idx) {
+                            itr.remove();
+                        }
+                    }
+                    assertTrue(elementCount >= (expectCount - threadCount));
+                    ThreadUtils.await(barrier);
+                }
+            });
+            t.setDaemon(true);
+            t.start();
+        }
+        ThreadUtils.await(barrier); //start
+        barrier.await(1L, TimeUnit.MINUTES);
+    }
+
+    private List<Object> populate() {
+        List<Object> elements = new ArrayList<Object>(ENTRY_COUNT);
+        for (int i = 0; i < ENTRY_COUNT; i++) {
+            Object o = new Object();
+            elements.add(o);
+            queue.add(o);
+        }
+        return elements;
+    }
+
+}

Loading...