diff --git a/openide.nodes/src/org/openide/nodes/Children.java b/openide.nodes/src/org/openide/nodes/Children.java --- a/openide.nodes/src/org/openide/nodes/Children.java +++ b/openide.nodes/src/org/openide/nodes/Children.java @@ -41,6 +41,9 @@ package org.openide.nodes; +import java.lang.ref.WeakReference; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -52,6 +55,8 @@ import java.util.List; import java.util.Set; import java.util.TreeSet; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Logger; import org.openide.util.Enumerations; import org.openide.util.Mutex; @@ -91,7 +96,7 @@ * needs for a certain amount of time to forbid modification, * he can execute his code in {@link Mutex#readAccess}. */ - public static final Mutex MUTEX = new Mutex(PR); + public static final Mutex MUTEX = new Mutex(PR, new ProjectManagerDeadlockDetector()); /** The object representing an empty set of children. Should * be used to represent the children of leaf nodes. The same @@ -1755,4 +1760,62 @@ }); } */ + + private static final class ProjectManagerDeadlockDetector implements Executor { + + private final AtomicReference> pmMutexRef = new AtomicReference>(); + + public void execute(Runnable command) { + boolean ea = false; + assert ea = true; + if (ea) { + Mutex mutex = getPMMutex(); + assert mutex != null; + if (mutex != null && (mutex.isReadAccess() || mutex.isWriteAccess())) { + throw new IllegalStateException("Should not acquire Children.MUTEX while holding ProjectManager.mutex()"); + } + } + command.run(); + } + + private Mutex getPMMutex() { + for (;;) { + Mutex mutex = null; + WeakReference weakRef = pmMutexRef.get(); + if (weakRef != null) { + mutex = weakRef.get(); + } + if (mutex != null) { + return mutex; + } + mutex = callPMMutexMethod(); + if (mutex != null) { + WeakReference newWeakRef = new WeakReference(mutex); + if (pmMutexRef.compareAndSet(weakRef, newWeakRef)) { + return mutex; + } + } else { + return null; + } + } + } + + private Mutex callPMMutexMethod() { + try { + Class clazz = Thread.currentThread().getContextClassLoader().loadClass("org.netbeans.api.project.ProjectManager"); // NOI18N + Method method = clazz.getMethod("mutex"); // NOI18N + return (Mutex) method.invoke(null); + } catch (ClassNotFoundException e) { + return null; + } catch (IllegalAccessException e) { + return null; + } catch (IllegalArgumentException e) { + return null; + } catch (InvocationTargetException e) { + return null; + } catch (NoSuchMethodException e) { + return null; + } + } + } } diff --git a/openide.nodes/test/unit/src/org/openide/nodes/ChildrenTest.java b/openide.nodes/test/unit/src/org/openide/nodes/ChildrenTest.java new file mode 100644 --- /dev/null +++ b/openide.nodes/test/unit/src/org/openide/nodes/ChildrenTest.java @@ -0,0 +1,115 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2008 Sun Microsystems, Inc. + */ + +package org.openide.nodes; + +import java.lang.ref.WeakReference; +import org.netbeans.junit.NbTestCase; +import org.openide.util.Mutex; + +/** + * + * @author Andrei Badea + */ +public class ChildrenTest extends NbTestCase { + + private static ClassLoader ccl; + + public ChildrenTest(String name) { + super(name); + } + + public void testProjectManagerDeadlockDetector() throws Exception { + ccl = Thread.currentThread().getContextClassLoader(); + ClassLoader loader = new PMClassLoader(); + Thread.currentThread().setContextClassLoader(loader); + try { + class NoOp implements Runnable { + public void run() {} + } + final int[] called = { 0 }; + Runnable tester = new Runnable() { + public void run() { + try { + called[0]++; + Children.MUTEX.readAccess(new NoOp()); + fail(); + } catch (IllegalStateException e) { + // OK. + } + try { + called[0]++; + Children.MUTEX.writeAccess(new NoOp()); + fail(); + } catch (IllegalStateException e) { + // OK. + } + } + }; + PM.mutex().readAccess(tester); + PM.mutex().writeAccess(tester); + assertEquals(4, called[0]); + } finally { + Thread.currentThread().setContextClassLoader(ccl); + } + WeakReference mutexRef = new WeakReference(PM.mutex); + PM.mutex = null; + assertGC("Should GC PM.mutex", mutexRef); + } + + private static final class PMClassLoader extends ClassLoader { + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + if (name.equals("org.netbeans.api.project.ProjectManager")) { + return PM.class; + } + throw new ClassNotFoundException(); + } + } + + private static final class PM { + + public static Mutex mutex = new Mutex(); + + public static Mutex mutex() { + return mutex; + } + } +}