尝试使 Nashorn 线程安全
Attempting to make Nashorn thread safe
我最近一直在使用 Play!框架和 Nashorn 试图呈现 Redux 应用程序。最初,我在 ThreadPoolExecutor 中实现了多个 Nashorn 引擎,并在 运行 engine.eval()
时使用了 futures。性能很糟糕,我假设是由于高 I/O 和我使用的阻塞未来。
在对 Play 越来越熟悉之后!和他们的 async/promise 模式,我试图从一个 ScriptEngine
开始;根据 ,脚本引擎本身是线程安全的,但绑定不是。这是 class 的完整内容:
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<String> 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<String> getContent(String path, String globalCode) {
try {
F.Promise<String> result = engine.getContent(path, globalCode);
return result.map((String r) -> r);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
我的 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$Recompilation5892286AA$\^eval\_.L:22918$L:22920$matchPattern(<eval>:23049)
at jdk.nashorn.internal.scripts.Script$Recompilation5886752AAAAAA$\^eval\_.L:24077$L:24079$matchRouteDeep(<eval>:24168)
at jdk.nashorn.internal.scripts.Script$Recompilation5870262DAA$\^eval\_.L:24077$L:24079$matchRoutes$L:24252$L:24253(<eval>:24254)
at jdk.nashorn.internal.scripts.Script$Recompilation5869060$\^eval\_.L:23848$loopAsync$next(<eval>:23869)
at jdk.nashorn.internal.scripts.Script$Recompilation5848906AAA$\^eval\_.L:23848$loopAsync(<eval>:23875)
at jdk.nashorn.internal.scripts.Script$Recompilation5830189$\^eval\_.L:24077$L:24079$matchRoutes$L:24252(<eval>:24253)
at jdk.nashorn.internal.scripts.Script$Recompilation5829862AAA$\^eval\_.L:24077$L:24079$matchRoutes(<eval>:24252)
at jdk.nashorn.internal.scripts.Script$Recompilation5809440AA$\^eval\_.L:23151$L:23153$useRoutes$L:23209$match(<eval>:23239)
at jdk.nashorn.internal.scripts.Script$Recompilation5187004AA$\^eval\_.L:25026$L:25028$match(<eval>:25084)
at jdk.nashorn.internal.scripts.Script$Recompilation46872AA$\^eval\_.L:53$renderApp(<eval>:147)
at jdk.nashorn.internal.scripts.Script467$\^eval\_.:program(<eval>: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[=11=](NashornEngine.java:48)
at play.core.j.FPromiseHelper$$anonfun$promise.apply(FPromiseHelper.scala:36)
at scala.concurrent.impl.Future$PromiseCompletingRunnable.liftedTree1(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)
我的印象是,使用已编译脚本创建新绑定将被视为引擎的全新内容,但事实并非如此。如果有的话,我在这里做错了什么?
我也尝试使用 Invocable
来调用 App
对象上的方法,但这没有任何区别。
编辑
我在一台具有超线程的 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;
}
}
很遗憾,您遇到了 bug in Nashorn. The good news is that it was fixed already, and the fix is available in current early access releases。
更新到最新版本的 Nashorn,或 Java 1.8u76 解决了这个问题。
我最近一直在使用 Play!框架和 Nashorn 试图呈现 Redux 应用程序。最初,我在 ThreadPoolExecutor 中实现了多个 Nashorn 引擎,并在 运行 engine.eval()
时使用了 futures。性能很糟糕,我假设是由于高 I/O 和我使用的阻塞未来。
在对 Play 越来越熟悉之后!和他们的 async/promise 模式,我试图从一个 ScriptEngine
开始;根据
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<String> 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<String> getContent(String path, String globalCode) {
try {
F.Promise<String> result = engine.getContent(path, globalCode);
return result.map((String r) -> r);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
我的 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$Recompilation5892286AA$\^eval\_.L:22918$L:22920$matchPattern(<eval>:23049)
at jdk.nashorn.internal.scripts.Script$Recompilation5886752AAAAAA$\^eval\_.L:24077$L:24079$matchRouteDeep(<eval>:24168)
at jdk.nashorn.internal.scripts.Script$Recompilation5870262DAA$\^eval\_.L:24077$L:24079$matchRoutes$L:24252$L:24253(<eval>:24254)
at jdk.nashorn.internal.scripts.Script$Recompilation5869060$\^eval\_.L:23848$loopAsync$next(<eval>:23869)
at jdk.nashorn.internal.scripts.Script$Recompilation5848906AAA$\^eval\_.L:23848$loopAsync(<eval>:23875)
at jdk.nashorn.internal.scripts.Script$Recompilation5830189$\^eval\_.L:24077$L:24079$matchRoutes$L:24252(<eval>:24253)
at jdk.nashorn.internal.scripts.Script$Recompilation5829862AAA$\^eval\_.L:24077$L:24079$matchRoutes(<eval>:24252)
at jdk.nashorn.internal.scripts.Script$Recompilation5809440AA$\^eval\_.L:23151$L:23153$useRoutes$L:23209$match(<eval>:23239)
at jdk.nashorn.internal.scripts.Script$Recompilation5187004AA$\^eval\_.L:25026$L:25028$match(<eval>:25084)
at jdk.nashorn.internal.scripts.Script$Recompilation46872AA$\^eval\_.L:53$renderApp(<eval>:147)
at jdk.nashorn.internal.scripts.Script467$\^eval\_.:program(<eval>: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[=11=](NashornEngine.java:48)
at play.core.j.FPromiseHelper$$anonfun$promise.apply(FPromiseHelper.scala:36)
at scala.concurrent.impl.Future$PromiseCompletingRunnable.liftedTree1(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)
我的印象是,使用已编译脚本创建新绑定将被视为引擎的全新内容,但事实并非如此。如果有的话,我在这里做错了什么?
我也尝试使用 Invocable
来调用 App
对象上的方法,但这没有任何区别。
编辑
我在一台具有超线程的 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;
}
}
很遗憾,您遇到了 bug in Nashorn. The good news is that it was fixed already, and the fix is available in current early access releases。
更新到最新版本的 Nashorn,或 Java 1.8u76 解决了这个问题。