I've recently been working with the Play! framework and Nashorn in an attempt to render a Redux application. Initially, I had implemented multiple Nashorn engines in a ThreadPoolExecutor and used futures when running engine.eval()
. The performance was terrible, I'm assuming due to the high I/O and the blocking future I was using.
我最近一直在玩Play!框架和Nashorn试图渲染Redux应用程序。最初,我在ThreadPoolExecutor中实现了多个Nashorn引擎,并在运行engine.eval()时使用了future。性能很糟糕,我假设由于高I / O和我使用的阻塞未来。
After becoming more familiar with Play! and their async/promise pattern, I attempted to start over with just a single ScriptEngine
; per https://stackoverflow.com/a/30159424/5956783, the script engine itself is thread safe, however bindings are not. Here is the class in its entirety:
在熟悉Play之后!和他们的异步/承诺模式,我试图只用一个ScriptEngine重新开始;根据https://stackoverflow.com/a/30159424/5956783,脚本引擎本身是线程安全的,但绑定不是。这是完整的课程:
package services;
import akka.actor.ActorSystem;
import play.libs.F;
import scala.concurrent.ExecutionContext;
import java.io.FileReader;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.script.*;
@Singleton
public class NashornEngine extends JSEngineAbstract {
private NashornThread engine;
private final ActorSystem actorSystem;
protected class NashornThread {
private ScriptEngine engine;
private final ExecutionContext executiOnContext= actorSystem.dispatchers().lookup("js-engine-executor");
private CompiledScript compiledScript;
public NashornThread() {
try {
String dir = System.getProperty("user.dir");
this.engine = new ScriptEngineManager(null).getEngineByName("nashorn");
this.compiledScript = ((Compilable) this.engine).compile(new FileReader(dir + "/public/Javascripts/bundle.js"));
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
public ScriptEngine getEngine() {
return engine;
}
public F.Promise getContent(String path, String globalCode) {
return F.Promise.promise(() -> this.executeMethod(path, globalCode), this.executionContext);
}
private String executeMethod(String path, String json) throws ScriptException {
Bindings newBinding = engine.createBindings();
try {
this.compiledScript.eval(newBinding);
getEngine().setBindings(newBinding, ScriptContext.ENGINE_SCOPE);
getEngine().eval("var global = this;");
getEngine().eval("var cOnsole= {log: print, error: print, warn: print};");
String result = getEngine().eval("App.renderApp('" + path + "', " + json + ")").toString();
return result;
} catch(Exception e) {
e.printStackTrace();
return null;
}
}
}
@Inject
public NashornEngine(ActorSystem actorSystem) {
this.actorSystem = actorSystem;
this.engine = new NashornThread();
}
@Override
public F.Promise getContent(String path, String globalCode) {
try {
F.Promise result = engine.getContent(path, globalCode);
return result.map((String r) -> r);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
My Javascript application exports an App
object with the method renderApp(path, state)
visible. This actually does work, but only 16 times (yes, always 16). Starting with iteration 17, I get the following exception and accompanying stack trace:
我的Javascript应用程序导出一个App对象,其方法renderApp(path,state)可见。这确实有效,但只有16次(是的,总是16次)。从迭代17开始,我得到以下异常并伴随着堆栈跟踪:
java.lang.ClassCastException: jdk.nashorn.internal.objects.NativeRegExpExecResult cannot be cast to jdk.nashorn.internal.objects.NativeArray
at jdk.nashorn.internal.objects.NativeArray.getContinuousArrayDataCCE(NativeArray.java:1900)
at jdk.nashorn.internal.objects.NativeArray.popObject(NativeArray.java:937)
at jdk.nashorn.internal.runtime.ScriptFunctionData.invoke(ScriptFunctionData.java:660)
at jdk.nashorn.internal.runtime.ScriptFunction.invoke(ScriptFunction.java:228)
at jdk.nashorn.internal.runtime.ScriptRuntime.apply(ScriptRuntime.java:393)
at jdk.nashorn.internal.scripts.Script$Recompilation$23589$782286AA$\^eval\_.L:22918$L:22920$matchPattern(:23049)
at jdk.nashorn.internal.scripts.Script$Recompilation$23588$816752AAAAAA$\^eval\_.L:24077$L:24079$matchRouteDeep(:24168)
at jdk.nashorn.internal.scripts.Script$Recompilation$23587$820262DAA$\^eval\_.L:24077$L:24079$matchRoutes$L:24252$L:24253(:24254)
at jdk.nashorn.internal.scripts.Script$Recompilation$23586$809060$\^eval\_.L:23848$loopAsync$next(:23869)
at jdk.nashorn.internal.scripts.Script$Recompilation$23584$808906AAA$\^eval\_.L:23848$loopAsync(:23875)
at jdk.nashorn.internal.scripts.Script$Recompilation$23583$820189$\^eval\_.L:24077$L:24079$matchRoutes$L:24252(:24253)
at jdk.nashorn.internal.scripts.Script$Recompilation$23582$819862AAA$\^eval\_.L:24077$L:24079$matchRoutes(:24252)
at jdk.nashorn.internal.scripts.Script$Recompilation$23580$789440AA$\^eval\_.L:23151$L:23153$useRoutes$L:23209$match(:23239)
at jdk.nashorn.internal.scripts.Script$Recompilation$23518$847004AA$\^eval\_.L:25026$L:25028$match(:25084)
at jdk.nashorn.internal.scripts.Script$Recompilation$23468$3872AA$\^eval\_.L:53$renderApp(:147)
at jdk.nashorn.internal.scripts.Script$23467$\^eval\_.:program(:1)
at jdk.nashorn.internal.runtime.ScriptFunctionData.invoke(ScriptFunctionData.java:640)
at jdk.nashorn.internal.runtime.ScriptFunction.invoke(ScriptFunction.java:228)
at jdk.nashorn.internal.runtime.ScriptRuntime.apply(ScriptRuntime.java:393)
at jdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:446)
at jdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:403)
at jdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:399)
at jdk.nashorn.api.scripting.NashornScriptEngine.eval(NashornScriptEngine.java:155)
at javax.script.AbstractScriptEngine.eval(AbstractScriptEngine.java:264)
at services.NashornEngine$NashornThread.executeMethod(NashornEngine.java:62)
at services.NashornEngine$NashornThread.lambda$getContent$0(NashornEngine.java:48)
at play.core.j.FPromiseHelper$$anonfun$promise$2.apply(FPromiseHelper.scala:36)
at scala.concurrent.impl.Future$PromiseCompletingRunnable.liftedTree1$1(Future.scala:24)
at scala.concurrent.impl.Future$PromiseCompletingRunnable.run(Future.scala:24)
at akka.dispatch.TaskInvocation.run(AbstractDispatcher.scala:40)
at akka.dispatch.ForkJoinExecutorConfigurator$AkkaForkJoinTask.exec(AbstractDispatcher.scala:397)
at scala.concurrent.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:260)
at scala.concurrent.forkjoin.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1339)
at scala.concurrent.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1979)
at scala.concurrent.forkjoin.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:107)
I am under the impression that creating a new binding with the compiled script would be treated as net-new to the engine, but that doesn't seem to be the case. What, if anything, am I doing wrong here?
我的印象是,使用已编译的脚本创建新绑定将被视为引擎的新网络,但似乎并非如此。如果有的话,我在这里做错了什么?
I have attempted to use an Invocable
as well to invoke the method on the App
object, but that doesn't make any difference.
我试图使用Invocable来调用App对象上的方法,但这没有任何区别。
EDIT I'm on an 8 core machine with hyper-threading, so that may explain the 16 successful attempts before the failed 17th attempt. Also, I've updated the executeMethod
method to below:
编辑我在一台带有超线程的8核机器上,这可以解释在第17次尝试失败之前的16次成功尝试。另外,我已将executeMethod方法更新为:
private String executeMethod(String path, String json) throws ScriptException {
Bindings newBinding = engine.createBindings();
try {
this.compiledScript.eval(newBinding);
getEngine().eval("var global = this;", newBinding);
getEngine().eval("var cOnsole= {log: print, error: print, warn: print};", newBinding);
Object app = getEngine().eval("App", newBinding);
Invocable invEngine = (Invocable) getEngine();
String result = invEngine.invokeMethod(app, "renderApp", path, json).toString();
// String result = getEngine().eval("App.renderApp('" + path + "', " + json + ")", newBinding).toString();
return result;
} catch(Exception e) {
e.printStackTrace();
return null;
}
}
1
Unfortunately you hit a bug in Nashorn. The good news is that it was fixed already, and the fix is available in current early access releases.
不幸的是你遇到了Nashorn的一个bug。好消息是它已经修复,并且修复程序在当前的早期访问版本中可用。
2
Updating to the latest version of Nashorn, or Java 1.8u76 fixes the issue.
更新到最新版本的Nashorn或Java 1.8u76可以解决此问题。