Classes generated by gen-class aren't loadable from remote codebase for mis-implementation of static-initializer
Description
Environment
Tested on Mac OS X 10.9.1 and Oracle JVM 1.7.0_51 with Clojure 1.6 master SNAPSHOT
Attachments
Activity
Added almost the same patch that does not have whitespace error.
I put a new patch applicable on the latest master branch.
This new patch is simpler and robust because the code-generation becomes very simple. Now It just call a method implemented with Java.
And I fixed my sample program and the 'HOW TO REPRODUCT THIS ISSUE' section of this ticket, because old description is not runnable on newest JVM. It is because the specification of remote method call of the newest JVM was changed from the old one. In the newest JVM, we need a 'java.rmi.server.useCodebaseOnly=false' option for making the behavior of remote call same as old one.
pull the newest sample.
Patch 20130204_fix_classloader.diff dated Feb 3, 2013 no longer applies cleanly as of the latest commits to Clojure master on Jan 11, 2014. The only conflict in applying the patch appears to be in the file src/jvm/clojure/asm/commons/GeneratorAdapter.java. This is probably due to the commit for ticket https://clojure.atlassian.net/browse/CLJ-713#icft=CLJ-713 that was committed today, updating the ASM library.
It seems overly complex to have the patch do so much code generation. Why not implement a method that does this job, and have the generated code call that?
This sounds reasonable, but anything touching classloaders must be considered very carefully.
When a genclass'ed object is serialized and sent to a remote system, the remote system throws an exception loading the object:
Exception in thread "main" java.lang.ExceptionInInitializerError at java.io.ObjectStreamClass.hasStaticInitializer(Native Method) at java.io.ObjectStreamClass.computeDefaultSUID(ObjectStreamClass.java:1723) at java.io.ObjectStreamClass.access$100(ObjectStreamClass.java:69) at java.io.ObjectStreamClass$1.run(ObjectStreamClass.java:247) at java.io.ObjectStreamClass$1.run(ObjectStreamClass.java:245) at java.security.AccessController.doPrivileged(Native Method) at java.io.ObjectStreamClass.getSerialVersionUID(ObjectStreamClass.java:244) at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:600) at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1601) at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1514) at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1750) at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1347) at java.io.ObjectInputStream.readObject(ObjectInputStream.java:369) at sun.rmi.server.UnicastRef.unmarshalValue(UnicastRef.java:324) at sun.rmi.server.UnicastRef.invoke(UnicastRef.java:173) at java.rmi.server.RemoteObjectInvocationHandler.invokeRemoteMethod(RemoteObjectInvocationHandler.java:194) at java.rmi.server.RemoteObjectInvocationHandler.invoke(RemoteObjectInvocationHandler.java:148) at $Proxy0.getResult(Unknown Source) at client.SampleClient$_main.doInvoke(SampleClient.clj:12) at clojure.lang.RestFn.invoke(RestFn.java:397) at clojure.lang.AFn.applyToHelper(AFn.java:159) at clojure.lang.RestFn.applyTo(RestFn.java:132) at client.SampleClient.main(Unknown Source) Caused by: java.io.FileNotFoundException: Could not locate remoteserver/SampleInterfaceImpl__init.class or remoteserver/SampleInterfaceImpl.clj on classpath: at clojure.lang.RT.load(RT.java:434) at clojure.lang.RT.load(RT.java:402) at clojure.core$load$fn__5039.invoke(core.clj:5520) at clojure.core$load.doInvoke(core.clj:5519) at clojure.lang.RestFn.invoke(RestFn.java:408) at clojure.lang.Var.invoke(Var.java:415) at remoteserver.SampleInterfaceImpl.<clinit>(Unknown Source) ... 23 more
Reproduce:
// build git clone git://github.com/tyano/clojure_genclass_fix.git cd clojure_genclass_fix sh build.sh // start rmiregistry rmiregistry -J-Djava.rmi.server.useCodebaseOnly=false & // start server cd remoteserver sh start.sh // Start client cd ../client sh start.sh
Cause:
A gen-classed class (in this case, SampleInterfaceImpl.class) uses a static-initializer for loading SampleInterfaceImpl__init.class like:
static { RT.var("clojure.core", "load").invoke("/remoteserver/SampleInterfaceImpl"); }
RT.load in default uses a context-classloader for loading __init.class but all classes depending on a gen-classed class must be loaded from the same classloader. In this case, RT.load must use a remote URLClassLoader which loads the main class.
Proposed:
Instead produce the equivalent of this in the static initializer:
static { Var.pushThreadBindings(RT.map(new Object[] { Compiler.LOADER, SampleInterfaceImpl.class.getClassLoader() })); try { RT.var("clojure.core", "load").invoke("/remoteserver/SampleInterfaceImpl"); } finally { Var.popThreadBindings(); } }
With this code, RT.load will uses a same classloader which load SampleInterfaceImpl.class.
Patch: clj-1157-v2.diff
Screened by: