Details
-
Type:
Defect
-
Status:
Open
-
Priority:
Major
-
Resolution: Unresolved
-
Affects Version/s: Release 1.4, Release 1.5
-
Fix Version/s: None
-
Component/s: None
-
Labels:None
-
Environment:Tested on Mac OS X 10.8 and Oracle JVM 1.7.0 update 13.
-
Patch:Code
-
Approval:Vetted
Description
When a client program uses a remote service which uses RMI, and the service returns a object which created with gen-class with clojure as the return value, the return value is not loadable at client side.
At client side, a following exeption will be thrown.
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
HOW TO REPRODUCT THIS ISSUE
If you want to see this issue at your computer, clone my example project from my github.
git clone git://github.com/tyano/clojure_genclass_fix.git
and build them (You must have installed Leiningen 2):
cd clojure_genclass_fix sh build.sh
start rmiregistry:
rmiregistry &
start remoteserver:
cd remoteserver sh start.sh
You will see a message "Server ready. " or "Server ready. (rebind)".
At last, start client program:
cd ../client sh start.sh
Without my patch, you will see a same Exception described above. But with clojure with my patch, you will see a right response message: "response = this is sample."
THE REASON
The reason of this problem is in bytecodes generated by gen-class. A gen-classed class (in this case, SampleInterfaceImpl.class) uses a static-initializer for loading SampleInterfaceImpl__init.class (which load other classes which implements functions in the class). The static-initializer is like bellow: (the following code is decompiled with JD - http://java.decompiler.free.fr/?q=jdgui )
static { RT.var("clojure.core", "load").invoke("/remoteserver/SampleInterfaceImpl"); }
Very simple code. it seems non-problematic. But RT.load changes the classloader for loading __init.class in the processing! RT.load in default uses a context-classloader for loading classes. But all classes depending on a gen-classed class must be loaded a same classloader with a main gen-classed class. In this case, RT.load must use a remote URLClassLoader which load a main class.
So, gen-class must be create bytecodes that is same with the following java code.
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.
My patch for gen-class will create bytecodes equal to the above example.
You can use an attached patch '20130204_fix_classloader.diff', or pull 'fix_classloader' branch from my github repositry ( git@github.com:tyano/clojure.git ).