Skip to content
Permalink
Browse files
[JENKINS-6604] Race condition in RemoteClassLoader.
  • Loading branch information
jglick committed Nov 22, 2012
1 parent 98b69cc commit fdd0f4bb1bc92fb68cb2dd5d0f5a8f80e19c78d9
@@ -29,6 +29,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.MalformedURLException;
import java.net.URLClassLoader;
@@ -40,6 +41,8 @@
import java.util.ArrayList;
import java.util.Set;
import java.util.HashSet;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* Loads class files from the other peer through {@link Channel}.
@@ -53,6 +56,8 @@
* @author Kohsuke Kawaguchi
*/
final class RemoteClassLoader extends URLClassLoader {

private static final Logger LOGGER = Logger.getLogger(RemoteClassLoader.class.getName());

/**
* Proxy to the code running on remote end.
@@ -126,10 +131,19 @@ along with the reference to the initiating ClassLoader (if the initiating ClassL
ClassLoader cl = channel.importedClassLoaders.get(cf.classLoader);
if (cl instanceof RemoteClassLoader) {
RemoteClassLoader rcl = (RemoteClassLoader) cl;
Class<?> c = rcl.findLoadedClass(name);
if (c==null)
c = rcl.loadClassFile(name,cf.classImage);
return c;
synchronized (_getClassLoadingLock(rcl, name)) {
Class<?> c = rcl.findLoadedClass(name);
if (TESTING) {
try {
Thread.sleep(1000);
} catch (InterruptedException x) {
assert false : x;
}
}
if (c==null)
c = rcl.loadClassFile(name,cf.classImage);
return c;
}
} else {
return cl.loadClass(name);
}
@@ -144,6 +158,31 @@ along with the reference to the initiating ClassLoader (if the initiating ClassL
}
}

private static Method gCLL;
static {
try {
gCLL = ClassLoader.class.getDeclaredMethod("getClassLoadingLock", String.class);
gCLL.setAccessible(true);
} catch (NoSuchMethodException x) {
// OK, Java 6
} catch (Exception x) {
LOGGER.log(Level.WARNING, null, x);
}
}
private static Object _getClassLoadingLock(RemoteClassLoader rcl, String name) {
// Java 7: return rcl.getClassLoadingLock(name);
if (gCLL != null) {
try {
return gCLL.invoke(rcl, name);
} catch (Exception x) {
LOGGER.log(Level.WARNING, null, x);
}
}
return rcl;
}
/** JENKINS-6604 */
static boolean TESTING;

private Class<?> loadClassFile(String name, byte[] bytes) {
// define package
definePackage(name);
@@ -29,6 +29,9 @@
import org.objectweb.asm.commons.EmptyVisitor;

import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.jvnet.hudson.test.Bug;

/**
* Test class image forwarding.
@@ -37,13 +40,13 @@
*/
public class ClassRemotingTest extends RmiTestBase {

private static final String CLASSNAME = "hudson.remoting.test.TestCallable";
private static final String CLASSNAME = "hudson.rem0ting.TestCallable";

public void test1() throws Throwable {
// call a class that's only available on DummyClassLoader, so that on the remote channel
// it will be fetched from this class loader and not from the system classloader.
DummyClassLoader cl = new DummyClassLoader(this.getClass().getClassLoader());
Callable c = (Callable) cl.loadClass("hudson.remoting.test.TestCallable").newInstance();
Callable c = (Callable) cl.loadClass(CLASSNAME).newInstance();

Object[] r = (Object[]) channel.call(c);

@@ -72,14 +75,45 @@ public void testRemoteProperty() throws Exception {
return;

DummyClassLoader cl = new DummyClassLoader(this.getClass().getClassLoader());
Callable c = (Callable) cl.loadClass("hudson.remoting.test.TestCallable").newInstance();
Callable c = (Callable) cl.loadClass(CLASSNAME).newInstance();
assertSame(c.getClass().getClassLoader(), cl);

channel.setProperty("test",c);

channel.call(new RemotePropertyVerifier());
}

@Bug(6604)
public void testRaceCondition() throws Throwable {
DummyClassLoader parent = new DummyClassLoader(ClassRemotingTest.class.getClassLoader());
DummyClassLoader child1 = new DummyClassLoader(parent, true);
final Callable<Object,Exception> c1 = (Callable) child1.loadClass(CLASSNAME + "$Sub").newInstance();
assertEquals(child1, c1.getClass().getClassLoader());
assertEquals(parent, c1.getClass().getSuperclass().getClassLoader());
DummyClassLoader child2 = new DummyClassLoader(parent, true);
final Callable<Object,Exception> c2 = (Callable) child2.loadClass(CLASSNAME + "$Sub").newInstance();
assertEquals(child2, c2.getClass().getClassLoader());
assertEquals(parent, c2.getClass().getSuperclass().getClassLoader());
ExecutorService svc = Executors.newFixedThreadPool(2);
RemoteClassLoader.TESTING = true;
try {
java.util.concurrent.Future<Object> f1 = svc.submit(new java.util.concurrent.Callable<Object>() {
public Object call() throws Exception {
return channel.call(c1);
}
});
java.util.concurrent.Future<Object> f2 = svc.submit(new java.util.concurrent.Callable<Object>() {
public Object call() throws Exception {
return channel.call(c2);
}
});
f1.get();
f2.get();
} finally {
RemoteClassLoader.TESTING = false;
}
}

public static Test suite() throws Exception {
return buildSuite(ClassRemotingTest.class);
}
@@ -23,15 +23,14 @@
*/
package hudson.remoting;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;

import java.io.IOException;
import java.io.InputStream;
import java.io.File;
import java.io.OutputStream;
import java.io.FileOutputStream;
import java.net.URL;
import org.apache.commons.io.IOUtils;

/**
* Used to load a dummy class <tt>hudson.remoting.test.TestCallable</tt>
@@ -40,16 +39,34 @@
* @author Kohsuke Kawaguchi
*/
class DummyClassLoader extends ClassLoader {

private final String logicalName;
private final String physicalPath;
private final String logicalPath;

public DummyClassLoader(ClassLoader parent) {
this(parent, false);
}

public DummyClassLoader(ClassLoader parent, boolean child) {
super(parent);
if (child) {
logicalName = "hudson.rem0ting.TestCallable$Sub";
physicalPath = "hudson/remoting/TestCallable$Sub.class";
logicalPath = "hudson/rem0ting/TestCallable$Sub.class";
} else {
logicalName = "hudson.rem0ting.TestCallable";
physicalPath = "hudson/remoting/TestCallable.class";
logicalPath = "hudson/rem0ting/TestCallable.class";
}
}


protected Class<?> findClass(String name) throws ClassNotFoundException {
if(name.equals("hudson.remoting.test.TestCallable")) {
if(name.equals(logicalName)) {
// rename a class
try {
byte[] bytes = loadTransformedClassImage(name);
byte[] bytes = loadTransformedClassImage();
return defineClass(name,bytes,0,bytes.length);
} catch (IOException e) {
throw new ClassNotFoundException("Bytecode manipulation failed",e);
@@ -59,28 +76,21 @@ public DummyClassLoader(ClassLoader parent) {
return super.findClass(name);
}

private byte[] loadTransformedClassImage(final String name) throws IOException {
InputStream in = getResourceAsStream("hudson/remoting/TestCallable.class");

// rename a class
ClassReader cr = new ClassReader(in);
ClassWriter w = new ClassWriter(cr,true) {
public void visit(int version, int access, String _name, String sig, String superName, String[] interfaces) {
super.visit(version, access, name.replace('.','/'), sig, superName, interfaces);
}
};
cr.accept(w,false);

return w.toByteArray();
private byte[] loadTransformedClassImage() throws IOException {
InputStream in = getResourceAsStream(physicalPath);
String data = IOUtils.toString(in, "ISO-8859-1");
// Single-character substitutions will not change length fields in bytecode etc.
String data2 = data.replaceAll("remoting(.)Test", "rem0ting$1Test");
return data2.getBytes("ISO-8859-1");
}


protected URL findResource(String name) {
if(name.equals("hudson/remoting/test/TestCallable.class")) {
if (name.equals(logicalPath)) {
try {
File f = File.createTempFile("rmiTest","class");
OutputStream os = new FileOutputStream(f);
os.write(loadTransformedClassImage("hudson.remoting.test.TestCallable"));
os.write(loadTransformedClassImage());
os.close();
f.deleteOnExit();
return f.toURI().toURL();
@@ -31,7 +31,7 @@
public class DummyClassLoaderTest extends TestCase {
public void testLoad() throws Throwable {
DummyClassLoader cl = new DummyClassLoader(this.getClass().getClassLoader());
Callable c = (Callable) cl.loadClass("hudson.remoting.test.TestCallable").newInstance();
Callable c = (Callable) cl.loadClass("hudson.rem0ting.TestCallable").newInstance();
System.out.println(c.call());
// make sure that the returned class is loaded from the dummy classloader
assertTrue(((Object[])c.call())[0].toString().startsWith(DummyClassLoader.class.getName()));
@@ -58,4 +58,6 @@ public Object call() throws Throwable {
return r;
}

public static class Sub extends TestCallable {}

}

0 comments on commit fdd0f4b

Please sign in to comment.