graaljs
graaljs copied to clipboard
Ability to change ClassLoader that is used to load polyglot and language implementations
Currently, it appears that Graal uses Thread.currentThread().getContextClassLoader()
to load the classes required for polyglot and language implementations. While this works fine in most scenarios, there are cases where it is desired to use a different ClassLoader than the current one.
In my use case, my code is running in a plugin environment, which uses its own separate ClassLoader implementation. Due to this, Graal is unable to find the classes required to run properly, resulting in the following error:
java.lang.IllegalStateException: No language and polyglot implementation was found on the classpath. Make sure the truffle-api.jar is on the classpath.
at org.graalvm.polyglot.Engine$PolyglotInvalid.noPolyglotImplementationFound(Engine.java:800) ~[?:?]
at org.graalvm.polyglot.Engine$PolyglotInvalid.buildEngine(Engine.java:732) ~[?:?]
at org.graalvm.polyglot.Engine$Builder.build(Engine.java:505) ~[?:?]
at cx.matthew.graaltest.GraalTestPlugin.onEnable(GraalTestPlugin.java:32) ~[?:?]
at org.bukkit.plugin.java.JavaPlugin.setEnabled(JavaPlugin.java:263) ~[patched_1.14.2.jar:git-Paper-97]
at org.bukkit.plugin.java.JavaPluginLoader.enablePlugin(JavaPluginLoader.java:338) ~[patched_1.14.2.jar:git-Paper-97]
at org.bukkit.plugin.SimplePluginManager.enablePlugin(SimplePluginManager.java:419) ~[patched_1.14.2.jar:git-Paper-97]
at org.bukkit.craftbukkit.v1_14_R1.CraftServer.enablePlugin(CraftServer.java:464) ~[patched_1.14.2.jar:git-Paper-97]
at org.bukkit.craftbukkit.v1_14_R1.CraftServer.enablePlugins(CraftServer.java:378) ~[patched_1.14.2.jar:git-Paper-97]
at net.minecraft.server.v1_14_R1.MinecraftServer.a(MinecraftServer.java:465) ~[patched_1.14.2.jar:git-Paper-97]
at net.minecraft.server.v1_14_R1.DedicatedServer.init(DedicatedServer.java:280) ~[patched_1.14.2.jar:git-Paper-97]
at net.minecraft.server.v1_14_R1.MinecraftServer.run(MinecraftServer.java:856) ~[patched_1.14.2.jar:git-Paper-97]
at java.lang.Thread.run(Thread.java:748) [?:1.8.0_212]
I'm aware that it's possible to manually add the classes to the classpath when running the main process, but this is not ideal because it required configuration on the end user's part that they may not be aware of and somewhat defeats the purpose of a simple plugin system.
There's also the workaround of changing the thread's context ClassLoader using Thread.currentThread().setContextClassLoader(classLoader)
whenever Graal is called, but this seems like a hacky way to make it work.
It'd be great if it was possible to specify a custom ClassLoader that Graal will use.
I ran into the same issue. We are migrating from Nashorn and use dedicated class loaders for JS scripts. It doesn't seem to work the Nashorn way which is to set the threads's context class loader to the required custom class loader before the engine is created:
ScriptEngineManager manager = new ScriptEngineManager();
Thread.currentThread().setContextClassLoader(createClassLoader());
ScriptEngine engine = null;
if (ISGRAAL)
engine = GraalSetup.engine();
The engine then "remembers" the class loader and uses it whenever it is invoked.
This is a show stopper for use since we dynamically load JDBC drivers in this manner.
The same problem in Quarkus after 1.3.0 release that has class loader changes
Since GraalVM 20.1 you can specify a hostClassLoader
when creating a Context
(via Context.newBuilder("js").hostClassLoader(....).build();
).
i have added Context.newBuilder("js").hostClassLoader(....).build(); but still getting java.lang.IllegalStateException: No language and polyglot implementation was found on the classpath. Make sure the truffle-api.jar is on the classpath.
at org.graalvm.polyglot.Engine$PolyglotInvalid.noPolyglotImplementationFound(Engine.java:873)
at org.graalvm.polyglot.Engine$PolyglotInvalid.buildEngine(Engine.java:794)
at org.graalvm.polyglot.Engine$Builder.build(Engine.java:546)
at com.oracle.truffle.js.scriptengine.GraalJSScriptEngine.
Hi @pramodanarase
can you please share a bit more about your setup. Ideally, what is your exact version and commandline you run java on and what additional setup do you use (maven, etc.)
Can you maybe share a minimized version of your application so we can inspect your code on our machines?
Thanks, Christian
Graal Js version - 21.0.0 JDK - AdoptOpenJDK 1.8.0_212 Classloader - Application Class Loader and Custom Child ClassLoader with ChildFirst classloading option
error at getGraalEngine() method. If we not set mainClassLoader
`public class GraalJSUtil {
private static final HostAccess hostAccess = HostAccess.newBuilder()
.allowListAccess(true)
.build();
public static Object evalGraalJS(String func) {
Object res = null;
// ClassLoader mainClassLoader = Thread.currentThread().getContextClassLoader();
//ClassLoader extensionClassLoader = GraalJSUtil.class.getClassLoader();
//Thread.currentThread().setContextClassLoader(extensionClassLoader);
GraalJSScriptEngine scriptEngine = getGraalEngine();
try {
Context graalContext = getGraalContext(scriptEngine);
Value bindingMap = graalContext.getBindings("js");
//set context variable
//update binding
// evalvuate function
Source source = GraalJSUtil.getSource(func);
res = GraalJSUtil.evalFunction(source, graalContext);
} finally {
clearGraalContext(scriptEngine);
// Thread.currentThread().setContextClassLoader(mainClassLoader);
}
return res;
}
private static Object evalFunction(Source source, Context graalContext) {
return graalContext.eval(source);
}
public static Context.Builder getGraalContextBuilder() {
return Context.newBuilder("js")
.hostClassLoader(GraalJSUtil.class.getClassLoader())
.allowHostAccess(hostAccess);
}
public static ScriptEngine getGraalEngine() {
ScriptEngine engine = null;
//ClassLoader mainClassLoader = Thread.currentThread().getContextClassLoader();
//ClassLoader extensionClassLoader = GraalJSUtil.class.getClassLoader();
//Thread.currentThread().setContextClassLoader(extensionClassLoader);
try {
engine = GraalJSScriptEngine.create(null, GraalJSUtil.getGraalContextBuilder());
} finally {
//Thread.currentThread().setContextClassLoader(mainClassLoader);
}
return engine;
}
} `
Hi @pramodanarase
thanks for your code. I'll have to dig deeper on this later, but there are two immediate observations I have:
JDK 8 On JDK 8, you won't get good performance out of Graal.js. On JDK8 - unless you use our custom GraalVM JDK8 build - the JavaScript interpreter will not be compiled to machine code but stay interpreted. If you use the GraalVM optimizing compiler (either by using GraalVM instead of OpenJDK as your JVM, or by manually integrating it on a JDK11+ as described in https://www.graalvm.org/reference-manual/js/RunOnJDK/ ) you will see a significant Graal.js performance boost. Depending on your usecase it can be as high as 10x or even 100x. Also, running on GraalVM will ensure that all the classes are set up properly and you don't land in class loader problems as you do (unless you manually specify a different setup).
ScriptEngine
You are mixing GraalVM's polyglot Context
and the legacy support for ScriptEngine
. This is possible, but complicates things and might actually have an impact on your problem. If you only ever use the ScriptEngine internally and route all the access through your evalGraalJS
method anyway, then you don't need the ScriptEngine at all (it is just a superfluous wrapper then). Just create a Context
and use context.eval("js", ...)
to execute your code. See https://www.graalvm.org/truffle/javadoc/org/graalvm/polyglot/Context.html#eval-java.lang.String-java.lang.CharSequence-
Best, Christian
Is there any known workaround in latest versions to load the language implementation using a classloader while allowing to load (host) classes with another? We are unable to update to latest versions as the workaround using the context class loader no longer works.
Is there any known workaround in latest versions to load the language implementation using a classloader while allowing to load (host) classes with another? We are unable to update to latest versions as the workaround using the context class loader no longer works.
The "we" in this case is OWASP ZAP - we use graaljs for JS scripting in ZAP and we are stuck on v20.2.0 because of this problem 😦
I believe we've fixed this in the upcoming GraalVM 22.3. Can you please try a recent developer build to see if the problem is resolved in that version?
I assume this is fixed. If not, feel free to reopen.
For the record, this is now working fine for us.