[CLJ-1157] Classes generated by gen-class aren't loadable from remote codebase for mis-implementation of static-initializer Created: 04/Feb/13 Updated: 12/Apr/13 |
|
| Status: | Open |
| Project: | Clojure |
| Component/s: | None |
| Affects Version/s: | Release 1.4, Release 1.5 |
| Fix Version/s: | None |
| Type: | Defect | Priority: | Major |
| Reporter: | Tsutomu Yano | Assignee: | Unassigned |
| Resolution: | Unresolved | Votes: | 0 |
| Labels: | None | ||
| Environment: |
Tested on Mac OS X 10.8 and Oracle JVM 1.7.0 update 13. |
||
| Attachments: |
|
| 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 ISSUEIf 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 REASONThe 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. 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 ). |
| Comments |
| Comment by Stuart Halloway [ 01/Mar/13 10:20 AM ] |
|
This sounds reasonable, but anything touching classloaders must be considered very carefully. |
| Comment by Stuart Halloway [ 01/Mar/13 12:12 PM ] |
|
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? |