Using === across different contexts

Attila Szegedi szegedia at gmail.com
Tue Dec 22 20:33:04 UTC 2015


Hi Vivin,

let me chime in with some answers for you.

We recommend using a single Nashorn ScriptEngine instance with your
application. I have some StackOverflow posts that try to explain it:

http://stackoverflow.com/questions/33690353/efficient-way-to-prepare-an-apache-commons-pool-of-nashorn-engines/34122641#34122641

http://stackoverflow.com/questions/30140103/should-i-use-a-separate-scriptengine-and-compiledscript-instances-per-each-threa/30159424#30159424

and maybe slightly less related but still helpful:

http://stackoverflow.com/questions/27494130/nashorn-inefficiency/27562223#27562223

Also, I spoke about this issue at several conferences this year, e.g.
here's the video of my GeekOut EE presentation: https://vimeo.com/131393786
If you don't want to watch it all, the relevant section starts around the
28th minute mark and lasts until minute 53.

To answer your questions:

 1. Does Nashorn optimize pre-compiled JS code each time it is evaluated?
>   Are the optimizations persisted to the compiled representation?
>

Yes. Especially if you cast your ScriptEngine to javax.script.Compilable
and obtain CompiledScript instances. If you evaluate a CompiledScript in a
new Bindings object, and your script mostly defines functions, it's very
cheap. It'll just instantiate function objects -- they're cheap, just a
tuple of (lexical scope, code) and the code was already precompiled and put
them into bindings.


>    2. If I repeatedly evaluate the same JS code in the same script-engine,
>    but in different contexts, is that code optimized? Or are the
> optimizations
>    discarded once the context is gone?


Code is maintained on a per-engine basis. If you create multiple Bindings
(or ScriptContexts) within a single engine, the code is shared.

Hope these help.

Attila.


On Tue, Dec 22, 2015 at 9:11 PM, Vivin Suresh Paliath <
vivin.paliath at gmail.com> wrote:

> Hi Sundar,
>
> Thank you for the explanation! I hope I am not being too obtuse - I'm just
> trying to get a handle on how things work!
>
> Object identity is implemented strictly per ECMAScript spec - which says
> > === evals to be evaluated true only if "same object".  Why should we
> treat
> > ScriptObjectMirrors as special for identity comparison
>
>
> In my view, ScriptObjectMirrors should be treated the same only if it is
> made abundantly clear that the JavaScript object you're working with in
> Nashorn, may sometimes be a wrapped object and not the actual object
> itself. The problem is that right now there is no easy way to discern that,
> and anticipate the side effects that come with that distinction. With
> respect to the ES spec, I agree that === should evaluate to true if the
> objects are the same object, and that holds true for the most part even if
> the objects are wrapped. But the problem arises when you try to compare a
> property of an object (which is itself a JS object) with itself, where the
> parent object happens to be foreign. That's when you end up in a situation
> where obj === obj returns true, but obj.prop === obj.prop returns false.
> The assumption I am making is invalid only if I *know* that they are mirror
> objects, but as I mentioned before, there is no easy way to determine that.
> I can see a case where obj.prop is actually a getter that returns a new
> instance of an object each time, and here obj.prop === obj.prop would
> return false, but that fact would be evident from the source of the JS
> object itself, and wouldn't be due to how foreign script-objects are
> implemented in Nashorn.
>
> For eg. think of using that in j.u.IdentityHashMap and expect it work! [===
> > returns true and so IdentityHashMap should work as expected -- but it
> won't
> > because IdentityHashMap goes by JVM level object identity!
>
>
> Perhaps I am not understanding this example correctly. Are you saying that
> inserts into the IdentityHashMap wouldn't work as expected?  But wouldn't
> the only comparison that is being performed be == in Java, which would be
> comparing the ScriptObjectMirror instances anyway (i.e., the same behavior
> as now). I tried out the following with and without my changes and I got
> the same output (size of the map is 3):
>
> engine.eval("function Foo(src) { this.src = src }; var e = { x: new
> Foo(\"what\") };");
>
>
> ScriptContext c = new SimpleScriptContext();
> c.setBindings(engine.createBindings(), ScriptContext.ENGINE_SCOPE);
>
> c.getBindings(ScriptContext.ENGINE_SCOPE).putAll(engine.getBindings(ScriptContext.ENGINE_SCOPE));
>
> c.getBindings(ScriptContext.ENGINE_SCOPE).put("ihm", new
> IdentityHashMap<Object, Object>());
> engine.eval("ihm.put(e.x, 1); ihm.put(e.x, 2); ihm.put(e.x, 3);
> print(ihm.size());", c);
>
>
> The one discrepancy that would exist is if you perform an === comparison in
> JavaScript against something from the map.
>
> You're creating multiple ScriptContexts and associate ENGINE_SCOPE Bindings
> > to isolate.  And yet you want objects from "isolated" worlds to be
> treated
> > exactly like "current isolate world".
>
>
> The new contexts I create know nothing about each other; they only "know"
> (in the sense that the "global" runtime-library objects are available) the
> objects from the main context (and only because I copy them over). Also, I
> am not treating the object from the main isolated-world the same way as the
> one in the current one; I am really only trying to compare references (and
> that too against themselves).
>
>
> > If you do not want that kind of multiple ECMAScript global scope
> isolation
> > at all, why not load your library/framework scripts before loading the
> user
> > scripts? If you want to avoid clash b/w different user scripts (i.e.,
> > globals in user's scripts for eg.), you could implement "require" like
> > primitive such as the one here
>
>
> This is actually exactly what I am doing, and I am using require-like
> functionality as well. The problem arises when I try to dereference a
> property of an object that is a foreign script-object. For example, let's
> assume the default context of the script engine has an "enum" SomeEnum,
> with the enum CONST. When I get a custom script, I create a new context
> (using createBindings) and copy everything over. So now the new context has
> a foreign object called SomeEnum. Now if I do SomeEnum === SomeEnum, I get
> back true, but SomeEnum.CONST === SomeEnum.CONST returns false. Is this
> what you were saying weak-refs would solve?
>
> If you do want isolation and still want singleton enum-like objects, why
> > not implement those in Java and expose to scripts? These would then be
> real
> > JVM singleton objects and therefore usual identity would work.
>
>
> That would be a workaround, but some code that I insert are autogenerated
> JS equivalents of POJOs. Enums are implemented using this
> <https://github.com/vivin/enumjs> library, which gives you type-safe enums
> in JavaScript as well. So I cannot assume upfront what kind of Java enums
> are going to be used since they come from a different project entirely.
>
> Based on what you said, I agree with you that === is probably the wrong
> place to do this and it seems that the thing that really needs to be fixed
> is ensuring that properties of foreign objects return the same mirror
> instance whenever dereferenced. As I said before, I can't think of a case
> where obj.prop === obj.prop would return false *unless* prop is a
> calculated property that returns a new instance of some object each time.
> Even if that were the case, I don't see how that behavior would be broken
> if the Nashorn runtime ensured that we got back the same mirror instance.
> Regardless, wouldn't it notice that prop actually delegates to a getter
> function, and then run that? But in the case where prop is a reference to
> an actual object, you would still have the expected behavior. I understand
> that these foreign objects are wrapped, but I am questioning whether
> someone really needs to *know* that they are, if they are writing JS code
> on Nashorn.
>
> My original aim when I was doing this was to provide a single script-engine
> instance so that I could take advantage of optimizations on common code,
> while providing isolated execution contexts. Objects in the engine's
> default context are "read-only" and the isolated contexts cannot really
> mess with them. Really what I'm after is ensuring that library-code gets
> optimized over time and that contexts can't step over each other. So if
> such an approach gives me that, then it would definitely make things a lot
> easier. Also, is there somewhere I can read about the optimizations Nashorn
> makes in different cases? I'm specifically wondering about the following:
>
>    1. Does Nashorn optimize pre-compiled JS code each time it is evaluated?
>    Are the optimizations persisted to the compiled representation?
>    2. If I repeatedly evaluate the same JS code in the same script-engine,
>    but in different contexts, is that code optimized? Or are the
> optimizations
>    discarded once the context is gone?
>
> If I can still get these optimizations, then I can simply just precompile
> and/or re-evaluate the library code in each new context and that would do
> away with this issue and make things much easier.
>
> Thank you once again, and I apologize for so many questions!
>
> Vivin
>
> On Tue, Dec 22, 2015 at 1:35 AM, Sundararajan Athijegannathan <
> sundararajan.athijegannathan at oracle.com> wrote:
>
> > Hi,
> >
> > May be I should explain the background on how nashorn treates
> > "foreign/host" objects.  Nashorn implements object access primitives like
> > "get_property, set_property, get_method, call, new" via a series of
> > dynalink linkers (http://openjdk.java.net/jeps/276). There is a linker
> > for it's own objects (ScriptObject instances), there is one for Java
> > objects ("beans linkers"), "foreign 'script' objects"
> > (ScriptObjectMirrors). It allows even user specified linkers picked up
> via
> > java.util.ServiceLoader mechanism. (See
> >
> http://hg.openjdk.java.net/jdk9/dev/nashorn/file/74ddd1339c57/samples/dynalink
> > ).
> >
> > But, looping (via for, for..each), identity and other operators are
> > natively implemented in Nashorn. Object identity is implemented strictly
> > per ECMAScript spec - which says === evals to be evaluated true only if
> > "same object".  Why should we treat ScriptObjectMirrors as special for
> > identity comparison (and who knows what problems it could cause than the
> > ones it solves!). For eg. think of using that in j.u.IdentityHashMap and
> > expect it work! [=== returns true and so IdentityHashMap should work as
> > expected -- but it won't because IdentityHashMap goes by JVM level object
> > identity! As I said, it at all we want mirrors to preserve identity, we
> > need weak refs.
> >
> > /
> >
> > Regarding host objects, even if they are different from native objects,
> it
> > seems strange to me that the semantics of things like === would be
> > different especially when doing something like obj.prop === obj.prop. The
> > fact that such a statement could ever return false is non-obvious unless
> > implementation details of the runtime are known.
> > /
> >
> > The fact that you're assuming your host objects return "same object" for
> > doing two "obj.prop" evals -- is an assumption about mirror objects! Why
> do
> > you think such as assumption to be true for mirrors -- whose sole purpose
> > is to provide easy access to objects.  You're creating multiple
> > ScriptContexts and associate ENGINE_SCOPE Bindings to isolate.  And yet
> you
> > want objects from "isolated" worlds to be treated exactly like "current
> > isolate world".
> >
> > If you do not want that kind of multiple ECMAScript global scope
> isolation
> > at all, why not load your library/framework scripts before loading the
> user
> > scripts? If you want to avoid clash b/w different user scripts (i.e.,
> > globals in user's scripts for eg.), you could implement "require" like
> > primitive such as the one here ->
> > https://github.com/walterhiggins/commonjs-modules-javax-script
> >
> > If you do want isolation and still want singleton enum-like objects, why
> > not implement those in Java and expose to scripts? These would then be
> real
> > JVM singleton objects and therefore usual identity would work.
> >
> > Hope this helps,
> > -Sundar
> >
> >
> > On 12/22/2015 12:25 PM, Vivin Suresh Paliath wrote:
> >
> > I am looking at it from the perspective of someone writing code that is
> > expect to run in  JavaScript environment. I will describe the runtime
> > environment I have set up. Perhaps what I am running into is an artifact
> of
> > my approach.
> >
> > I have a single script engine instance. In this context I evaluate some
> JS
> > source that populates the JS global scope with some objects. When a
> custom
> > script needs to be evaluated, I create a new script context with a new
> > global. Then I copy over all bindings from the parent script context into
> > the new one. I also have a module system. When a custom script requests a
> > core module, the module source is evaluated in the main script context
> and
> > a single instance of the object exposed by the module source is cached.
> > This way the source only needs to be evaluated once and benefits from
> > optimizations.
> >
> > The problem I am running into has to do with some singleton objects I
> > expose; kind of like enums, and can be compared using ===. I am using a
> JS
> > library for this and this behavior holds in the browser and on NodeJS. On
> > Nashorn, because the instances were created in a different context, ===
> > returns false.
> >
> > Is there a better way to accomplish what I am doing?
> >
> > Regarding host objects, even if they are different from native objects,
> it
> > seems strange to me that the semantics of things like === would be
> > different especially when doing something like obj.prop === obj.prop. The
> > fact that such a statement could ever return false is non-obvious unless
> > implementation details of the runtime are known.
> >
> > I am trying to expose my runtime environment as a JS environment where a
> > developer can write custom scripts using some runtime libraries and
> > utilities. So behavior like this would be very surprising, and requiring
> > them to know that objects might be from different contexts seems like
> > abstraction leakage from the way I have implemented the runtime. They
> > shouldn't have to know whether an object is a wrapped ScriptObjectMirror
> > instance, since from their point of view they are just working with JS
> > objects, and they don't know that they are from different "worlds" since
> > they don't even know that there are different worlds.
> >
> > I guess I am also having a hard time seeing a scenario where === would
> > need to return false in the scenario I described, unless as a developer
> you
> > knew there were different worlds. Even then, it would essentially make
> some
> > forms of equality comparisons impossible (e.g., o.prop === o.prop if o is
> > foreign).
> >
> > Does the snippet I posted earlier make sense?
> > On Dec 21, 2015 9:10 PM, "Sundararajan Athijegannathan" <
> > sundararajan.athijegannathan at oracle.com> wrote:
> >
> >> Actually, I'm not sure if depending on === in the code is a good
> approach
> >> -- particularly, for objects  that are not script objects of the current
> >> world. These are to be treated like "host objects" in ECMAScript-speak.
> >> i.e., regular rules of script objects don't always apply to 'host
> >> objects'.  The === operator for Java objects is interpreted as object
> >> identity -- and the same rule for ScriptObjectMirrors -- in that both
> are
> >> "host objects" from the standpoint of the 'current world'.
> >>
> >> -Sundar
> >>
> >> On 12/22/2015 9:34 AM, Vivin Suresh Paliath wrote:
> >>
> >> Thanks for the response! I understand that in general it would be
> >> difficult for foreign objects. But this seems like surprising behavior
> >> given that these are both JavaScript objects. That they are unequal
> seems
> >> to be an artifact of the implementation (the fact that JS objects from
> >> different contexts are treated as foreign). Could JS objects be treated
> >> differently?
> >>
> >> After going through the Nashorn source, I decided to try this very naive
> >> approach adding the following test to ScriptRuntime#equalSameTypeValues:
> >>
> >> if(x instanceof ScriptObjectMirror && y instanceof ScriptObjectMirror) {
> >>     return x.equals(y);
> >> }
> >>
> >> After this change, the code now returns true, because
> >> ScriptObjectMirror#equals compares the actual objects. I am not sure if
> >> this breaks anything though (been trying to run the test suite, but end
> up
> >> getting some errors from make). Is there a reason this particular fix
> is a
> >> bad idea? I can't think of a particular reason why. From the
> perspective of
> >> the runtime, I can't see a reason why those two objects should be
> >> considered different.
> >>
> >> I will investigate the eval approach, but ideally I would like something
> >> that doesn't impose any changes on the JS code. I am developing a simple
> >> runtime that exposes some objects and utilities, and where custom
> scripts
> >> can run in their own contexts. The fact that === returns false in these
> >> cases leads to some very strange behavior.
> >>
> >> On Mon, Dec 21, 2015 at 8:48 PM, Sundararajan Athijegannathan <
> >> <sundararajan.athijegannathan at oracle.com>
> >> sundararajan.athijegannathan at oracle.com> wrote:
> >>
> >>> Unless we create mirrors as weak refs internally (i.e., maintain 1:1
> >>> with underlying foreign object reference), there is no easy solution.
> And
> >>> maintaining such weak refs is unnecessarily complex. "Foreign object"
> >>> call/access is mean to be just a "lightweight wrapper" based access.
> That
> >>> said, you can do the === on the foreign context itself. You can call
> >>> ScriptObjectMirror's eval to evaluate === test in that foreign context.
> >>> That would get right object identity.
> >>>
> >>> -Sundar
> >>>
> >>>
> >>> On 12/22/2015 4:26 AM, Vivin Suresh Paliath wrote:
> >>>
> >>>> One more thing I noticed is that apparently a new ScriptObjectMirror
> >>>> instance is probably being created each time x is dereferenced, so
> "e.x
> >>>> ===
> >>>> e.x" also returns "false".
> >>>>
> >>>> On Mon, Dec 21, 2015 at 3:49 PM, Vivin Suresh Paliath <
> >>>> vivin.paliath at gmail.com> wrote:
> >>>>
> >>>> I ran into an issue where === returns false even when both should be
> >>>>> pointing to the same object. I'm assuming this is because one of the
> >>>>> objects is wrapped by a ScriptObjectMirror, because it was defined
> in a
> >>>>> different context.
> >>>>>
> >>>>> Here's some code that demonstrates this:
> >>>>>
> >>>>>          ScriptEngine engine = new
> >>>>> NashornScriptEngineFactory().getScriptEngine(
> >>>>>              new String[] { "-strict" }
> >>>>>          );
> >>>>>
> >>>>>          try {
> >>>>>              engine.eval("function Foo(src) { this.src = src }; var e
> >>>>> = {
> >>>>> x: new Foo(\"what\") };");
> >>>>>
> >>>>>              ScriptContext c = new SimpleScriptContext();
> >>>>>              c.setBindings(engine.createBindings(),
> >>>>> ScriptContext.ENGINE_SCOPE);
> >>>>>
> >>>>>
> >>>>>
> c.getBindings(ScriptContext.ENGINE_SCOPE).putAll(engine.getBindings(ScriptContext.ENGINE_SCOPE));
> >>>>>
> >>>>>              System.out.println(engine.eval("var z = e.x; z ===
> e.x;",
> >>>>> c));
> >>>>>          } catch(Exception e) {
> >>>>>              throw new RuntimeException(e);
> >>>>>          }
> >>>>>
> >>>>> This prints out "false". Is there a way around this? I am also
> >>>>> explicitly
> >>>>> copying over all the bindings from the parent scope into the new
> scope
> >>>>> so
> >>>>> that I have access to "e". Could this be the source of the problem,
> >>>>> and if
> >>>>> so, is there a better way to achieve what I'm trying to do?
> >>>>>
> >>>>> --
> >>>>> Ruin untold;
> >>>>> And thine own sadness,
> >>>>> Sing in the grass,
> >>>>> When eve has forgot, that no more hear common things that gleam and
> >>>>> pass;
> >>>>> But seek alone to lip, sad Rose of love and ruin untold;
> >>>>> And thine own mother
> >>>>> Can know it as I know
> >>>>> More than another
> >>>>> What makes your own sadness,
> >>>>> Set in her eyes.
> >>>>>
> >>>>> map{@n=split//;$j.=$n[0]x$n[1]}split/:/,"01:11:02".
> >>>>> ":11:01:11:02:13:01:11:01:11:01:13:02:12:01:13:01".
> >>>>> ":11:04:11:06:12:04:11:01:12:01:13:02:12:01:14:01".
> >>>>> ":13:01:11:03:12:01:11:04:12:02:11:01:11:01:13:02".
> >>>>> ":11:03:11:06:11:01:11:05:12:02:11:01:11:01:13:02".
> >>>>> ":11:02:12:01:12:04:11:06:12:01:11:04:12:04:11:01".
> >>>>> ":12:03:12:01:12:01:11:01:12:01:12:02:11:01:11:01".
> >>>>> ":13:02:11:01:02:11:01:12:02";map{print chr unpack"
> >>>>> i",pack"B32",$_}$j=~m/.{8}/g
> >>>>>
> >>>>>
> >>>>
> >>>>
> >>>
> >>
> >>
> >> --
> >> Ruin untold;
> >> And thine own sadness,
> >> Sing in the grass,
> >> When eve has forgot, that no more hear common things that gleam and
> pass;
> >> But seek alone to lip, sad Rose of love and ruin untold;
> >> And thine own mother
> >> Can know it as I know
> >> More than another
> >> What makes your own sadness,
> >> Set in her eyes.
> >>
> >> map{@n=split//;$j.=$n[0]x$n[1]}split/:/,"01:11:02".
> >> ":11:01:11:02:13:01:11:01:11:01:13:02:12:01:13:01".
> >> ":11:04:11:06:12:04:11:01:12:01:13:02:12:01:14:01".
> >> ":13:01:11:03:12:01:11:04:12:02:11:01:11:01:13:02".
> >> ":11:03:11:06:11:01:11:05:12:02:11:01:11:01:13:02".
> >> ":11:02:12:01:12:04:11:06:12:01:11:04:12:04:11:01".
> >> ":12:03:12:01:12:01:11:01:12:01:12:02:11:01:11:01".
> >> ":13:02:11:01:02:11:01:12:02";map{print chr unpack"
> >> i",pack"B32",$_}$j=~m/.{8}/g
> >>
> >>
> >>
> >
>
>
> --
> Ruin untold;
> And thine own sadness,
> Sing in the grass,
> When eve has forgot, that no more hear common things that gleam and pass;
> But seek alone to lip, sad Rose of love and ruin untold;
> And thine own mother
> Can know it as I know
> More than another
> What makes your own sadness,
> Set in her eyes.
>
> map{@n=split//;$j.=$n[0]x$n[1]}split/:/,"01:11:02".
> ":11:01:11:02:13:01:11:01:11:01:13:02:12:01:13:01".
> ":11:04:11:06:12:04:11:01:12:01:13:02:12:01:14:01".
> ":13:01:11:03:12:01:11:04:12:02:11:01:11:01:13:02".
> ":11:03:11:06:11:01:11:05:12:02:11:01:11:01:13:02".
> ":11:02:12:01:12:04:11:06:12:01:11:04:12:04:11:01".
> ":12:03:12:01:12:01:11:01:12:01:12:02:11:01:11:01".
> ":13:02:11:01:02:11:01:12:02";map{print chr unpack"
> i",pack"B32",$_}$j=~m/.{8}/g
>


More information about the nashorn-dev mailing list