From 5768da22a214d1198472aab9dae0e0148efcbbdb Mon Sep 17 00:00:00 2001 From: kohsuke Date: Fri, 21 Aug 2009 18:13:30 +0000 Subject: [PATCH] Fixed a NoClassDefFoundError problem that happens in remoting+maven+3rd plugin combo. report git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@21002 71c3de6d-444a-0410-be80-ed276b4c234a --- .../main/java/hudson/remoting/Capability.java | 42 +++++++ .../main/java/hudson/remoting/Channel.java | 14 ++- .../remoting/MultiClassLoaderSerializer.java | 110 ++++++++++++++++++ .../hudson/remoting/ObjectInputStreamEx.java | 4 +- .../java/hudson/remoting/UserRequest.java | 30 ++++- 5 files changed, 191 insertions(+), 9 deletions(-) create mode 100644 remoting/src/main/java/hudson/remoting/Capability.java create mode 100644 remoting/src/main/java/hudson/remoting/MultiClassLoaderSerializer.java diff --git a/remoting/src/main/java/hudson/remoting/Capability.java b/remoting/src/main/java/hudson/remoting/Capability.java new file mode 100644 index 0000000000..a7c43aad40 --- /dev/null +++ b/remoting/src/main/java/hudson/remoting/Capability.java @@ -0,0 +1,42 @@ +package hudson.remoting; + +import java.io.Serializable; + +/** + * Represents additional features implemented on {@link Channel}. + * + *

+ * Each {@link Channel} exposes its capability to {@link Channel#getProperty(Object)}. + * + *

+ * This mechanism allows two different versions of remoting.jar to talk to each other. + * + * @author Kohsuke Kawaguchi + * @see Channel#remoteCapability + */ +final class Capability implements Serializable { + /** + * Bit mask of optional capabilities. + */ + private final long mask; + + Capability(long mask) { + this.mask = mask; + } + + Capability() { + this(1); + } + + /** + * Does this implementation supports multi-classloader serialization in + * {@link UserRequest}? + * + * @see MultiClassLoaderSerializer + */ + boolean supportsMultiClassLoaderRPC() { + return (mask&1)!=0; + } + + private static final long serialVersionUID = 1L; +} diff --git a/remoting/src/main/java/hudson/remoting/Channel.java b/remoting/src/main/java/hudson/remoting/Channel.java index 3f6e2534da..f6f5e0c8ca 100644 --- a/remoting/src/main/java/hudson/remoting/Channel.java +++ b/remoting/src/main/java/hudson/remoting/Channel.java @@ -184,6 +184,11 @@ public class Channel implements VirtualChannel, IChannel { */ private IChannel remoteChannel; + /** + * Capability of the remote {@link Channel}. + */ + final Capability remoteCapability; + /** * Communication mode. * @since 1.161 @@ -285,18 +290,20 @@ public class Channel implements VirtualChannel, IChannel { this.name = name; this.executor = exec; this.isRestricted = restricted; - ObjectOutputStream oos = null; if(export(this,false)!=1) throw new AssertionError(); // export number 1 is reserved for the channel itself remoteChannel = RemoteInvocationHandler.wrap(this,1,IChannel.class,false,false); + properties.put(Capability.class,new Capability()); // export our capability + // write the magic preamble. // certain communication channel, such as forking JVM via ssh, // may produce some garbage at the beginning (for example a remote machine // might print some warning before the program starts outputting its own data.) // // so use magic preamble and discard all the data up to that to improve robustness. + ObjectOutputStream oos = null; if(mode!= Mode.NEGOTIATE) { os.write(mode.preamble); oos = new ObjectOutputStream(mode.wrap(os)); @@ -331,6 +338,11 @@ public class Channel implements VirtualChannel, IChannel { this.ois = new ObjectInputStream(mode.wrap(is)); new ReaderThread(name).start(); + + Capability rc = (Capability) getRemoteProperty(Capability.class); + if (rc==null) rc = new Capability(0); // assume no capability + this.remoteCapability = rc; + return; } } else { diff --git a/remoting/src/main/java/hudson/remoting/MultiClassLoaderSerializer.java b/remoting/src/main/java/hudson/remoting/MultiClassLoaderSerializer.java new file mode 100644 index 0000000000..6eb5c827c1 --- /dev/null +++ b/remoting/src/main/java/hudson/remoting/MultiClassLoaderSerializer.java @@ -0,0 +1,110 @@ +package hudson.remoting; + +import hudson.remoting.RemoteClassLoader.IClassLoader; + +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectStreamClass; +import java.io.OutputStream; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * {@link ObjectInputStream}/{@link ObjectOutputStream} pair that can handle object graph that spans across + * multiple classloaders. + * + * @author Kohsuke Kawaguchi + * @see Capability#supportsMultiClassLoaderRPC() + */ +class MultiClassLoaderSerializer { + static final class Output extends ObjectOutputStream { + private final Channel channel; + /** + * Encountered Classloaders, to their indices. + */ + private final Map classLoaders = new HashMap(); + + Output(Channel channel, OutputStream out) throws IOException { + super(out); + this.channel = channel; + } + + + protected void annotateClass(Class c) throws IOException { + ClassLoader cl = c.getClassLoader(); + if (cl==null) {// bootstrap classloader. no need to export. + writeInt(-2); + return; + } + + Integer idx = classLoaders.get(cl); + if (idx==null) { + classLoaders.put(cl,classLoaders.size()); + writeInt(-1); + writeObject(RemoteClassLoader.export(cl,channel)); + } else { + writeInt(idx); + } + } + + @Override + protected void annotateProxyClass(Class cl) throws IOException { + annotateClass(cl); + } + } + + static final class Input extends ObjectInputStream { + private final Channel channel; + private final List classLoaders = new ArrayList(); + + Input(Channel channel, InputStream in) throws IOException { + super(in); + this.channel = channel; + } + + private ClassLoader readClassLoader() throws IOException, ClassNotFoundException { + int code = readInt(); + switch (code) { + case -2: + return null; + case -1: + // fill the entry with some value in preparation of recursive readObject below. + // this is actually only necessary for classLoader[0]. + classLoaders.add(Channel.class.getClassLoader()); + + ClassLoader cl = channel.importedClassLoaders.get((IClassLoader) readObject()); + classLoaders.set(classLoaders.size()-1,cl); + return cl; + default: + return classLoaders.get(code); + } + } + + @Override + protected Class resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { + String name = desc.getName(); + try { + return Class.forName(name, false, readClassLoader()); + } catch (ClassNotFoundException ex) { + return super.resolveClass(desc); + } + } + + @Override + protected Class resolveProxyClass(String[] interfaces) throws IOException, ClassNotFoundException { + ClassLoader cl = readClassLoader(); + + Class[] classes = new Class[interfaces.length]; + for (int i = 0; i < interfaces.length; i++) + classes[i] = Class.forName(interfaces[i], false, cl); + return Proxy.getProxyClass(cl, classes); + } + } +} diff --git a/remoting/src/main/java/hudson/remoting/ObjectInputStreamEx.java b/remoting/src/main/java/hudson/remoting/ObjectInputStreamEx.java index f3a3d32e1b..b3c6f21044 100644 --- a/remoting/src/main/java/hudson/remoting/ObjectInputStreamEx.java +++ b/remoting/src/main/java/hudson/remoting/ObjectInputStreamEx.java @@ -29,13 +29,11 @@ import java.io.ObjectInputStream; import java.io.ObjectStreamClass; import java.lang.reflect.Modifier; import java.lang.reflect.Proxy; -import java.util.Map; -import java.util.HashMap; /** * {@link ObjectInputStream} that uses a specific class loader. */ -final class ObjectInputStreamEx extends ObjectInputStream { +class ObjectInputStreamEx extends ObjectInputStream { private final ClassLoader cl; public ObjectInputStreamEx(InputStream in, ClassLoader cl) throws IOException { diff --git a/remoting/src/main/java/hudson/remoting/UserRequest.java b/remoting/src/main/java/hudson/remoting/UserRequest.java index fac09915a4..e59da2c6d0 100644 --- a/remoting/src/main/java/hudson/remoting/UserRequest.java +++ b/remoting/src/main/java/hudson/remoting/UserRequest.java @@ -33,6 +33,7 @@ import java.io.IOException; import java.io.ObjectOutputStream; import java.io.Serializable; import java.io.NotSerializableException; +import java.io.ObjectInputStream; /** * {@link Request} that can take {@link Callable} whose actual implementation @@ -84,7 +85,7 @@ final class UserRequest extends Request extends Request extends Request implements Serializable { public RSP retrieve(Channel channel, ClassLoader cl) throws IOException, ClassNotFoundException, EXC { Channel old = Channel.setCurrent(channel); try { - Object o = new ObjectInputStreamEx(new ByteArrayInputStream(response), cl).readObject(); + Object o = UserRequest.deserialize(channel,response,cl); if(isException) throw (EXC)o; -- GitLab