You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by jw...@apache.org on 2017/02/11 17:00:16 UTC
groovy git commit: GROOVY-8067: Possible deadlock when creating new
ClassInfo entries in the cache (closes #489)
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 <jw...@apache.org>
Authored: Sat Feb 11 08:17:44 2017 -0800
Committer: John Wagenleitner <jw...@apache.org>
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;
+ }
+
+}