-
-
Notifications
You must be signed in to change notification settings - Fork 925
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix JRuby embed crash after application reloading #4312
Conversation
We use JRuby `ScriptingContainer` in a tomcat servlet web application. After application reloading in tomcat, JRuby runtime initialization fails with the error message. "BUG: could not initialize constructor handle" `MethodHandles.publicLookup().findConstructor()` fails with `IllegalAccessException` in static initialization block. The initialization does not work well with tomcat reloading. As a workaround `MethodHandles.lookup()` without access check works fine.
Here is a test code for the issue. With jruby-complete-9.1.6.0.jar, this fails with the following Exception. import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
public class TestReload {
public static void main(String[] args) {
try {
for (int i = 0; i < 2; i++) {
// load class
URLClassLoader loader = new URLClassLoader(new URL[] {
new URL("jar:file:./jruby-complete.jar!/")
});
Class<?> container = loader.loadClass("org.jruby.embed.ScriptingContainer");
Method runScriptlet = container.getMethod("runScriptlet", String.class);
Method setClassLoader = container.getMethod("setClassLoader", ClassLoader.class);
Object c = container.newInstance();
setClassLoader.invoke(c, loader);
runScriptlet.invoke(c, "puts \"Hello World!\"");
// unload class
loader = null;
c = null;
System.gc();
}
} catch (Exception e) {
e.printStackTrace();
}
}
} $ java TestReload
Hello World!
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at TestReload.main(TestReload.java:18)
Caused by: java.lang.ExceptionInInitializerError
at org.jruby.runtime.ThreadContext.<init>(ThreadContext.java:201)
at org.jruby.runtime.ThreadContext.newContext(ThreadContext.java:76)
at org.jruby.internal.runtime.ThreadService.initMainThread(ThreadService.java:166)
at org.jruby.Ruby.init(Ruby.java:1238)
at org.jruby.Ruby.newInstance(Ruby.java:339)
at org.jruby.embed.internal.AbstractLocalContextProvider.getGlobalRuntime(AbstractLocalContextProvider.java:82)
at org.jruby.embed.internal.SingletonLocalContextProvider.getRuntime(SingletonLocalContextProvider.java:99)
at org.jruby.embed.internal.EmbedRubyRuntimeAdapterImpl.runParser(EmbedRubyRuntimeAdapterImpl.java:167)
at org.jruby.embed.internal.EmbedRubyRuntimeAdapterImpl.parse(EmbedRubyRuntimeAdapterImpl.java:94)
at org.jruby.embed.ScriptingContainer.parse(ScriptingContainer.java:1239)
at org.jruby.embed.ScriptingContainer.runScriptlet(ScriptingContainer.java:1299)
... 5 more
Caused by: java.lang.RuntimeException: BUG: could not initialize constructor handle
at org.jruby.runtime.scope.ManyVarsDynamicScope.<clinit>(ManyVarsDynamicScope.java:39)
... 16 more |
Threads would be needed to unload loaded classes. import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
public class TestReload {
public static void main(String[] args) {
while (true) {
Thread t = new Thread() {
public void run() {
try (URLClassLoader loader = new URLClassLoader(new URL[] {
new URL("jar:file:./jruby-complete.jar!/")
})) {
// load class
Class<?> container = loader.loadClass("org.jruby.embed.ScriptingContainer");
Method runScriptlet = container.getMethod("runScriptlet", String.class);
Method setClassLoader = container.getMethod("setClassLoader", ClassLoader.class);
Method terminate = container.getMethod("terminate");
Object c = container.newInstance();
setClassLoader.invoke(c, loader);
runScriptlet.invoke(c, "puts \"Hello World!\"");
terminate.invoke(c);
} catch (Exception e) {
e.printStackTrace();
}
// unload class
System.gc();
System.runFinalization();
System.gc();
}
};
t.start();
try {
t.join();
} catch (Exception e) {
e.printStackTrace();
}
}
}
} $ java -XX:+TraceClassUnloading TestReload
(snip)
[thread 9492 also had an error]
#
# A fatal error has been detected by the Java Runtime Environment:
#
# EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x000000006e4c24b0, pid=11608, tid=0x00000000000031e4
#
# JRE version: Java(TM) SE Runtime Environment (8.0_111-b14) (build 1.8.0_111-b14)
# Java VM: Java HotSpot(TM) 64-Bit Server VM (25.111-b14 mixed mode windows-amd64 compressed oops)
# Problematic frame:
# C 0x000000006e4c24b0
#
# Failed to write core dump. Minidumps are not enabled by default on client versions of Windows
#
# An error report file with more information is saved as:
(snip) hs_err_pid11608.log contains:
|
Nice investigation on this one. I'll try out your reproduction code and see if I can figure out what's going wrong. |
Reproduced! Investigating. |
Using MethodHandles.lookup() does indeed solve the issue, and I think it's a good change. The jnr-ffi crash is a separate issue and I'll investigate that. |
We use JRuby
ScriptingContainer
in a tomcat servlet webapplication. After application reloading in tomcat, JRuby
runtime initialization fails with the error message.
"BUG: could not initialize constructor handle"
MethodHandles.publicLookup().findConstructor()
fails withIllegalAccessException
in static initialization block.The initialization does not work well with tomcat reloading.
As a workaround
MethodHandles.lookup()
without access checkworks fine.