Re-rolled: Reduces chances of deadlock when CompilingClassLoader and MultiParentClassLoader are concurrently accessed from multiple threads.

Review by: zundel@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10452 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java b/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
index 213b4dd..6b05280 100644
--- a/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
+++ b/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
@@ -70,6 +70,7 @@
 import java.util.SortedSet;
 import java.util.Stack;
 import java.util.TreeSet;
+import java.util.concurrent.locks.ReentrantLock;
 
 /**
  * An isolated {@link ClassLoader} for running all user code. All user files are
@@ -353,23 +354,11 @@
 
     public MultiParentClassLoader(ClassLoader parent, ClassLoader resources) {
       super(parent);
+      assert parent != null;
       this.resources = resources;
     }
 
     @Override
-    public Class<?> loadClass(String name) throws ClassNotFoundException {
-      try {
-        return getParent().loadClass(name);
-      } catch (Throwable t) {
-        // Make a second attempt not only on ClassNotFoundExceptions, but also errors like 
-        // ClassCircularityError
-        Class c = findClass(name);
-        resolveClass(c);
-        return c;
-      }
-    }
-    
-    @Override
     protected synchronized Class<?> findClass(String name)
         throws ClassNotFoundException {
       String resourceName = name.replace('.', '/') + ".class";
@@ -380,6 +369,28 @@
       byte[] bytes = Util.readURLAsBytes(url);
       return defineClass(name, bytes, 0, bytes.length);
     }
+    
+    @Override
+    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+      try {
+        Class c = findLoadedClass(name);
+        if (c != null) {
+          if (resolve) {
+            resolveClass(c);
+          }
+          return c;
+        }
+        return getParent().loadClass(name);
+      } catch (Throwable t) {
+        // Make a second attempt not only on ClassNotFoundExceptions, but also errors like 
+        // ClassCircularityError
+        Class c = findClass(name);
+        if (resolve) {
+          resolveClass(c);
+        }
+        return c;
+      }
+    }    
   }
 
   /**
@@ -706,6 +717,11 @@
   }
 
   /**
+   * Only loads bootstrap classes, specifically excluding classes from the classpath. 
+   */
+  private static final ClassLoader bootstrapClassLoader = new ClassLoader(null) { };
+  
+  /**
    * The names of the bridge classes.
    */
   private static final Map<String, Class<?>> BRIDGE_CLASS_NAMES = new HashMap<String, Class<?>>();
@@ -902,6 +918,8 @@
    */
   private boolean isInjectingClass = false;
 
+  private final ReentrantLock loadLock = new ReentrantLock();
+  
   private final TreeLogger logger;
 
   private final Set<String> scriptOnlyClasses = new HashSet<String>();
@@ -1044,8 +1062,7 @@
   }
 
   @Override
-  protected synchronized Class<?> findClass(String className)
-      throws ClassNotFoundException {
+  protected Class<?> findClass(String className) throws ClassNotFoundException {
     if (className == null) {
       throw new ClassNotFoundException("null class name",
           new NullPointerException());
@@ -1057,87 +1074,132 @@
       // happens to look.
       return ClassLoader.getSystemClassLoader().loadClass(className);
     }
-    
-    if (scriptOnlyClasses.contains(className)) {
-      // Allow the child ClassLoader to handle this
-      throw new ClassNotFoundException();
-    }
 
-    // Check for a bridge class that spans hosted and user space.
-    if (BRIDGE_CLASS_NAMES.containsKey(className)) {
-      return BRIDGE_CLASS_NAMES.get(className);
-    }
+    loadLock.lock();
+    try {
 
-    // Get the bytes, compiling if necessary.
-    byte[] classBytes = findClassBytes(className);
-    if (classBytes == null) {
-      throw new ClassNotFoundException(className);
-    }
-
-    if (HasAnnotation.hasAnnotation(classBytes, GwtScriptOnly.class)) {
-      scriptOnlyClasses.add(className);
-      maybeInitializeScriptOnlyClassLoader();
-      return Class.forName(className, true, scriptOnlyClassLoader);
-    }
-
-    /*
-     * Prevent reentrant problems where classes that need to be injected have
-     * circular dependencies on one another via JSNI and inheritance. This check
-     * ensures that a class's supertype can refer to the subtype (static
-     * members, etc) via JSNI references by ensuring that the Class for the
-     * subtype will have been defined before injecting the JSNI for the
-     * supertype.
-     */
-    boolean localInjection;
-    if (!isInjectingClass) {
-      localInjection = isInjectingClass = true;
-    } else {
-      localInjection = false;
-    }
-
-    Class<?> newClass = defineClass(className, classBytes, 0, classBytes.length);
-    if (className.equals(JavaScriptHost.class.getName())) {
-      javaScriptHostClass = newClass;
-      updateJavaScriptHost();
-    }
-
-    /*
-     * We have to inject the JSNI code after defining the class, since dispId
-     * assignment is based around reflection on Class objects. Don't inject JSNI
-     * when loading a JSO interface class; just wait until the implementation
-     * class is loaded.
-     */
-    if (!classRewriter.isJsoIntf(className)) {
-      CompilationUnit unit = getUnitForClassName(canonicalizeClassName(className));
-      if (unit != null) {
-        toInject.push(unit);
+      if (scriptOnlyClasses.contains(className)) {
+        // Allow the child ClassLoader to handle this
+        throw new ClassNotFoundException();
       }
-    }
 
-    if (localInjection) {
-      try {
+      // Check for a bridge class that spans hosted and user space.
+      if (BRIDGE_CLASS_NAMES.containsKey(className)) {
+        return BRIDGE_CLASS_NAMES.get(className);
+      }
+
+      // Get the bytes, compiling if necessary.
+      byte[] classBytes = findClassBytes(className);
+      if (classBytes == null) {
+        throw new ClassNotFoundException(className);
+      }
+
+      if (HasAnnotation.hasAnnotation(classBytes, GwtScriptOnly.class)) {
+        scriptOnlyClasses.add(className);
+        maybeInitializeScriptOnlyClassLoader();
+
         /*
-         * Can't use an iterator here because calling injectJsniFor may cause
-         * additional entries to be added.
+         * Release the lock before side-loading from scriptOnlyClassLoader. This prevents deadlock
+         * conditions when a class from scriptOnlyClassLoader ends up trying to call back into this
+         * classloader from another thread. 
          */
-        while (toInject.size() > 0) {
-          CompilationUnit unit = toInject.remove(0);
-          if (!alreadyInjected.contains(unit)) {
-            injectJsniMethods(unit);
-            alreadyInjected.add(unit);
-          }
+        loadLock.unlock();
+
+        // Also don't run the static initializer to lower the risk of deadlock. 
+        return Class.forName(className, false, scriptOnlyClassLoader);
+      }
+
+     /*
+      * Prevent reentrant problems where classes that need to be injected have
+      * circular dependencies on one another via JSNI and inheritance. This check
+      * ensures that a class's supertype can refer to the subtype (static
+      * members, etc) via JSNI references by ensuring that the Class for the
+      * subtype will have been defined before injecting the JSNI for the
+      * supertype.
+      */
+      boolean localInjection;
+      if (!isInjectingClass) {
+        localInjection = isInjectingClass = true;
+      } else {
+        localInjection = false;
+      }
+
+      Class<?> newClass = defineClass(className, classBytes, 0, classBytes.length);
+      if (className.equals(JavaScriptHost.class.getName())) {
+        javaScriptHostClass = newClass;
+        updateJavaScriptHost();
+      }
+
+      /*
+      * We have to inject the JSNI code after defining the class, since dispId
+      * assignment is based around reflection on Class objects. Don't inject JSNI
+      * when loading a JSO interface class; just wait until the implementation
+      * class is loaded.
+      */
+      if (!classRewriter.isJsoIntf(className)) {
+        CompilationUnit unit = getUnitForClassName(canonicalizeClassName(className));
+        if (unit != null) {
+          toInject.push(unit);
         }
-      } finally {
-        isInjectingClass = false;
+      }
+
+      if (localInjection) {
+        try {
+          /*
+          * Can't use an iterator here because calling injectJsniFor may cause
+          * additional entries to be added.
+          */
+          while (toInject.size() > 0) {
+            CompilationUnit unit = toInject.remove(0);
+            if (!alreadyInjected.contains(unit)) {
+              injectJsniMethods(unit);
+              alreadyInjected.add(unit);
+            }
+          }
+        } finally {
+          isInjectingClass = false;
+        }
+      }
+
+      if (className.equals("com.google.gwt.core.client.GWT")) {
+        gwtClass = newClass;
+        updateGwtClass();
+      }
+
+      return newClass;
+    } finally {
+      if (loadLock.isLocked()) {
+        loadLock.unlock();
       }
     }
+  }
 
-    if (className.equals("com.google.gwt.core.client.GWT")) {
-      gwtClass = newClass;
-      updateGwtClass();
+  /**
+   * Remove some of the excess locking that we'd normally inherit from loadClass.
+   */
+  @Override
+  protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+    Class c = findLoadedClass(name);
+    if (c != null) {
+      if (resolve) {
+        resolveClass(c);
+      }
+      return c;
     }
-
-    return newClass;
+    
+    assert getParent() == null;
+    
+    try {
+      c = bootstrapClassLoader.loadClass(name);
+    } catch (ClassNotFoundException e) {
+      c = findClass(name);
+    }
+    
+    if (resolve) {
+      resolveClass(c);
+    }
+    
+    return c;
   }
 
   void clear() {