Index: core/src/org/netbeans/core/modules/ModuleManager.java =================================================================== RCS file: /cvs/core/src/org/netbeans/core/modules/ModuleManager.java,v --- core/src/org/netbeans/core/modules/ModuleManager.java 15 Nov 2001 11:46:03 -0000 1.17 +++ core/src/org/netbeans/core/modules/ModuleManager.java 25 Jul 2002 19:23:01 -0000 @@ -65,6 +65,7 @@ public ModuleManager(ModuleInstaller installer, Events ev) { this.installer = installer; this.ev = ev; + updateContextClassLoaders(classLoader, true); } /** Access for ManifestSection. */ @@ -239,8 +240,43 @@ } synchronized (classLoaderLock) { classLoader = nue; + updateContextClassLoaders(classLoader, false); } firer.change(new ChangeFirer.Change(this, PROP_CLASS_LOADER, null, null)); + } + private static void updateContextClassLoaders(ClassLoader l, boolean force) { + // See #20663. + ThreadGroup g = Thread.currentThread().getThreadGroup(); + while (g.getParent() != null) g = g.getParent(); + // Now g is the master thread group, hopefully. + // See #4097747 for an explanation of the convoluted logic. + while (true) { + int s = g.activeCount() + 1; + Thread[] ts = new Thread[s]; + int x = g.enumerate(ts, true); + if (x < s) { + // We got all of the threads, good. + for (int i = 0; i < x; i++) { + // The first time when we make the manager, set all of the + // threads to have this context classloader. Let's hope no + // threads needing a special context loader have been started + // yet! On subsequent occasions, when the classloader + // changes, we update all threads for which setContextClassLoader + // has not been called with some other special classloader. + if (force || (ts[i].getContextClassLoader() instanceof SystemClassLoader)) { + //Util.err.log("Setting ctxt CL on " + ts[i].getName() + " to " + l); + ts[i].setContextClassLoader(l); + } else { + Util.err.log("Not touching context class loader " + ts[i].getContextClassLoader() + " on thread " + ts[i].getName()); + } + } + Util.err.log("Set context class loader on " + x + " threads"); + break; + } else { + Util.err.log("Race condition getting all threads, restarting..."); + continue; + } + } } /** A classloader giving access to all the module classloaders at once. */ Index: core/test/unit/src/org/netbeans/core/modules/ModuleManagerTest.java =================================================================== RCS file: /cvs/core/test/unit/src/org/netbeans/core/modules/ModuleManagerTest.java,v --- core/test/unit/src/org/netbeans/core/modules/ModuleManagerTest.java 9 Oct 2001 12:27:02 -0000 1.10 +++ core/test/unit/src/org/netbeans/core/modules/ModuleManagerTest.java 25 Jul 2002 19:23:02 -0000 @@ -21,6 +21,7 @@ import java.lang.reflect.*; import org.openide.util.*; import org.openide.modules.*; +import java.net.*; /** Test the module manager as well as the Module class. * This means creating modules from JAR as well as from "classpath" @@ -424,6 +425,116 @@ Object o = c.newInstance(); assertEquals(25, f.getInt(o)); } finally { + mgr.mutexPrivileged().exitWriteAccess(); + } + } + + /** Test #20663: the context classloader is set on all threads + * according to the system classloader. + */ + public void testContextClassLoader() throws Exception { + FakeModuleInstaller installer = new FakeModuleInstaller(); + FakeEvents ev = new FakeEvents(); + final ModuleManager mgr = new ModuleManager(installer, ev); + mgr.mutexPrivileged().enterWriteAccess(); + // Make sure created threads do not die. + final Object sleepForever = "sleepForever"; + try { + final Module m1 = mgr.create(new File(jars, "simple-module.jar"), null, false, false); + Module m2 = mgr.create(new File(jars, "depends-on-simple-module.jar"), null, false, false); + ClassLoader l1 = mgr.getClassLoader(); + assertEquals(l1, Thread.currentThread().getContextClassLoader()); + mgr.enable(m1); + ClassLoader l2 = mgr.getClassLoader(); + assertTrue(l1 == l2); + assertEquals(l2, Thread.currentThread().getContextClassLoader()); + mgr.enable(m2); + ClassLoader l3 = mgr.getClassLoader(); + assertTrue(l1 == l3); + assertEquals(l3, Thread.currentThread().getContextClassLoader()); + mgr.disable(m2); + ClassLoader l4 = mgr.getClassLoader(); + assertTrue(l1 != l4); + assertEquals(l4, Thread.currentThread().getContextClassLoader()); + final Thread[] t23 = new Thread[2]; + final ClassLoader[] lx = new ClassLoader[] {new URLClassLoader(new URL[0])}; + // Make sure t1 runs to completion, though. + final Object finishT1 = "finishT1"; + Thread t1 = new Thread("custom thread #1") { + public void run() { + synchronized (finishT1) { + t23[0] = new Thread("custom thread #2") { + public void run() { + synchronized (sleepForever) { + try { + sleepForever.wait(); + } catch (InterruptedException ie) { + throw new Error(ie.toString()); + } + } + } + }; + t23[0].start(); + Thread.currentThread().setContextClassLoader(lx[0]); + mgr.disable(m1); + t23[1] = new Thread("custom thread #3") { + public void run() { + synchronized (sleepForever) { + try { + sleepForever.wait(); + } catch (InterruptedException ie) { + throw new Error(ie.toString()); + } + } + } + }; + t23[1].start(); + finishT1.notify(); + } + synchronized (sleepForever) { + try { + sleepForever.wait(); + } catch (InterruptedException ie) { + throw new Error(ie.toString()); + } + } + } + }; + t1.start(); + synchronized (finishT1) { + if (t23[1] == null) { + finishT1.wait(); + assertNotNull(t23[1]); + } + } + assertTrue(!m1.isEnabled()); + ClassLoader l5 = mgr.getClassLoader(); + assertTrue(l1 != l5); + assertTrue(l4 != l5); + assertEquals(l5, Thread.currentThread().getContextClassLoader()); + // It had a special classloader when we changed modules. + assertTrue(t1.isAlive()); + assertEquals(lx[0], t1.getContextClassLoader()); + // It was created before the special classloader. + assertTrue(t23[0].isAlive()); + assertEquals(l5, t23[0].getContextClassLoader()); + // It was created after and should have inherited the special classloader. + assertTrue(t23[1].isAlive()); + assertEquals(lx[0], t23[1].getContextClassLoader()); + mgr.enable(m1); + mgr.disable(m1); + ClassLoader l6 = mgr.getClassLoader(); + assertTrue(l1 != l6); + assertTrue(l4 != l6); + assertTrue(l5 != l6); + assertEquals(l6, Thread.currentThread().getContextClassLoader()); + assertEquals(lx[0], t1.getContextClassLoader()); + assertEquals(l6, t23[0].getContextClassLoader()); + assertEquals(lx[0], t23[1].getContextClassLoader()); + } finally { + synchronized (sleepForever) { + sleepForever.notifyAll(); + } mgr.mutexPrivileged().exitWriteAccess(); } } Index: openide/openide-spec-vers.properties =================================================================== RCS file: /cvs/openide/openide-spec-vers.properties,v --- openide/openide-spec-vers.properties 18 Apr 2002 00:25:14 -0000 1.38.8.2.4.1 +++ openide/openide-spec-vers.properties 25 Jul 2002 19:23:02 -0000 @@ -5,4 +5,4 @@ # Must always be numeric (numbers separated by '.', e.g. 1.11.3). # See http://openide.netbeans.org/versioning-policy.html for more. -org.openide.specification.version=1.43.3 +org.openide.specification.version=1.43.3.1