More fun with scopes and ScriptObjectMirror
Attila Szegedi
attila.szegedi at oracle.com
Wed Dec 11 03:30:07 PST 2013
Admittedly, I've not been working a lot with the scoping/multiple globals/javax.script API side of the project; Sundar is probably the one with best insight here (I've mostly worked on dynamic linking and Java platform APIs, and am lately focused on bytecode compiler and performance improvements targetted for the first post-8.0 update).
Having said that, I think it should be fairly easy to implement a CommonJS require() function within a single ScriptEngine instance, using separate ENGINE_SCOPE level bindings for loading each module and having them use a single global (setting the "--global-per-engine" flag that you can pass to one of NashornScriptEngine.getEngine() overloads that takes a String[] args).
As far as I know, when multiple engine scopes in a single ScriptEngine are sharing a single Global (effectively recreating the "MGN" model from <http://wiki.commonjs.org/wiki/Modules/ProposalForNativeExtension>), those scopes can accept each others' objects without ScriptObjectMirror wrapping. (ScriptObject-> ScriptObjectMirror wrapping only happens for scopes that don't share the Global object). You could execute every module with separate engine scope bindings (setting those bindings up with "require", "export", "module" variables for that module.) thus achieving isolation of their namespaces except for the built-in globals.
The obvious limitation of the approach is that execution within a single ScriptEngine is not threadsafe. In a multithreaded system you'd have to make sure they're either thread local, or access to them is synchronized. And you'd have your modules be loaded separately into every ScriptEngine. I'm not familiar with Vert.x architecture; I know folks working on Avatar.js didn't have problems with implementing require() in their system, but their system is single-threaded.
ScriptObjectMirror is obviously not as capable as ScriptObject. Mind you, I'm not using "obviously" meaning "it should've been obvious to everyone" but rather in the sense of "obvious now that you have stumbled across it". The reality of product development scheduling is that we can't address it in the current phase (see <http://openjdk.java.net/projects/jdk8/milestones>) even if we decided that it should be addressed. In ideal world, a mirror wouldn't be needed, but due to JavaScript's inherent entanglement of execution semantics with a set of highly mutable built-ins (Function, String, Array, etc.) they currently seem like a necessity when crossing Global boundaries.
I'd suggest you try to work with an approach that avoids creation of mirrors - where each ScriptEngine instance is an isolated execution context organized around a Global (this is true for JavaScript runtimes in general). Mirrors are an attempt on our part to provide cross-Global usage of JS objects, but as you have found out, they aren't complete first-class citizens when they end up in a different Global environments.
The abovementioned approach with "--global-per-engine", single-threaded usage of every engine instance, and use of separate ENGINE_SCOPE bindings in the engine using ScriptEngine.setBindings() is the best I can recommend at the moment.
Attila.
On Dec 10, 2013, at 9:16 PM, Tim Fox <timvolpe at gmail.com> wrote:
> Attila-
>
> Perhaps a more fundamental question is, is what I am trying to do (implement commonJS require) really possible in Vanilla Nashorn?
>
> I did notice this post by you in December 2012:
>
> http://mail.openjdk.java.net/pipermail/nashorn-dev/2012-December/000014.html
>
> I understand there is no intention to implement require() directly in Nashorn, but my previous understanding was that it should be possible to implement it myself on top of Nashorn given that I can create different scopes and pass JS objects between them. After my recent experiments I'm not sure that understanding is really correct!
>
> What's your view on this?
>
>
>
>
> On 10/12/13 19:49, Tim Fox wrote:
>> Sorry for the deluge of posts, but I think I've found another issue with scopes/ScriptObjectMirror.
>>
>> Consider this case: https://gist.github.com/purplefox/7896892
>>
>> I have a simple JS object which contains a function 'setCallback' which simply delegates to a Java object which also has a setCallback method.
>>
>> Calling this method works fine from within the same scope that it was created in.
>>
>> However, if I export the object to another scope - wrapping in ScriptObjectMirror as advised (and this is exactly the kind of thing I would need to do to implement CommonJS require in Nashorn), and then try to call the setCallback method from there, it yields the following exception:
>>
>> java.lang.ClassCastException: Cannot cast jdk.nashorn.api.scripting.ScriptObjectMirror to java.lang.Runnable
>> at sun.invoke.util.ValueConversions.newClassCastException(ValueConversions.java:461)
>> at sun.invoke.util.ValueConversions.castReference(ValueConversions.java:456)
>> at jdk.nashorn.internal.scripts.Script$\^eval\_._L2(<eval>:3)
>> at jdk.nashorn.internal.runtime.ScriptFunctionData.invoke(ScriptFunctionData.java:500)
>> at jdk.nashorn.internal.runtime.ScriptFunction.invoke(ScriptFunction.java:207)
>> at jdk.nashorn.internal.runtime.ScriptRuntime.apply(ScriptRuntime.java:378)
>> at jdk.nashorn.api.scripting.ScriptObjectMirror.call(ScriptObjectMirror.java:107)
>> at jdk.nashorn.internal.scripts.Script$\^eval\_.runScript(<eval>:1)
>> at jdk.nashorn.internal.runtime.ScriptFunctionData.invoke(ScriptFunctionData.java:498)
>> at jdk.nashorn.internal.runtime.ScriptFunction.invoke(ScriptFunction.java:207)
>> at jdk.nashorn.internal.runtime.ScriptRuntime.apply(ScriptRuntime.java:378)
>> at jdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:544)
>> at jdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:526)
>> at jdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:522)
>> at jdk.nashorn.api.scripting.NashornScriptEngine.eval(NashornScriptEngine.java:193)
>> at org.vertx.java.platform.impl.HandlerScopeTest.run(HandlerScopeTest.java:48)
>> at org.vertx.java.platform.impl.HandlerScopeTest.main(HandlerScopeTest.java:9)
>> 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:483)
>> at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)
>>
>
More information about the nashorn-dev
mailing list