You could still have GLOBAL_SCOPE Bindings (or any intermediate scope that you may introduce in your impl. of ScriptContext) between those contexts be different. Even ENGINE_SCOPE Bindings could be different -- say a SimpleBindings instance. Single shared Nashorn global is associated with that ENGINE_SCOPE. But when an undefined global var is searched from script, after searching the nashorn's Global, search continues in the Bindings of ScriptContext. So, you can continue to expose different variable values.

Example:

    ScriptContext defCtx = engine.getContext();
    defCtx.getBindings(ScriptContext.GLOBAL_SCOPE).put("foo", "hello");

    ScriptContext myCtx = new SimpleScriptContext();
myCtx.setBindings(defCtx.getBindings(ScriptContext.ENGINE_SCOPE), ScriptContext.ENGINE_SCOPE);
    Bindings b = new SimpleBindings();
    b.put("foo", "world");
    myCtx.setBindings(b, ScriptContext.GLOBAL_SCOPE);

     engine.eval("print(foo)"); // prints 'hello'
     engine.eval("print(foo)", myCtx); // prints "world"
     engine.eval("print(foo)", defCtx); // prints "hello"

PS. compilable test attached.

-Sundar


On Thursday 12 December 2013 02:19 PM, Attila Szegedi wrote:
On Dec 12, 2013, at 8:00 AM, Tim Fox <[email protected]> wrote:

On 11/12/13 12:53, Attila Szegedi wrote:
On Dec 11, 2013, at 1:13 PM, Tim Fox <[email protected]> wrote:

Confused...

I assumed that if two scripts where run with their own script context, then 
they would already have separate globals, i.e. if I do

myglobal = 1

in module 1 that won't be visible in module 2.
That's true, but then you also end up with the need for ScriptObjectMirrors 
between them, and that was what I suggested you try to avoid.

So I'm not sure really what --global-per-engine really means, if the modules 
have their own globals anyway. I guess my understanding must be wrong somewhere.
Well, it will mean that modules won't have their own globals… 
--global-per-engine will make it so that the Global object is specific to the 
ScriptEngine, and not to the ScriptContext, e.g. even if you replace the 
ScriptContext of the engine, when scripts are run, they'll still see the same 
global object as before the replacement. The gist of it is:

a) without --global-per-engine, the Global object is specific to a 
ScriptContext, each ScriptContext has its own. ENGINE_SCOPE Bindings object of 
the context is actually a mirror of its Global.
b) with --global-per-engine, the Global object lives in the ScriptEngine. 
ENGINE_SCOPE Bindings object of the context is just a vanilla SimpleBindings 
(or whatever you set it to), and Global will delegate property getters for 
non-existent properties to it (but it'll still receive property setters, so new 
global variables created by one script will be visible by another; no isolation 
there).
One more question on this. With --global-per-engine, and multiple ScriptContext 
instances - if all the ScriptContext instances share a single a single global, 
how is this different from just having a single ScriptContext in which you 
execute all JavaScript?
Not much. Using separate ScriptContexts with single engine is pretty much equivalent to modifying 
the initial ScriptContext of the engine. When I thought you'll end up using Java code to implement 
module loading, I suggested either using separate contexts, or at least changing the ENGINE_SCOPE 
bindings of the original context to contain "module", "exports", etc. but if 
you now implement those as parameters of an anonymous function that you put around the module 
source code and eval(), then you don't need even that.

So, yeah, you can have a single context for all JavaScript. As I said earlier, 
it'll be single-threaded, but JavaScript the language is by nature 
single-threaded (that's why node.js is single-threaded too). Probably the 
better way to say it is that JavaScript language has no defined multithreaded 
semantics, and thus a JavaScript execution environment is unsafe to use in a 
multithreaded manner. You can always have multiple engines – either per-thread 
or pooled – but they'll be completely isolated from one another, and modules 
and other code will end up being loaded separately in them. If you want shared 
state between them, you can always plug some shared Java objects into their 
ENGINE_SCOPE bindings or have them access some stateful statics, and then 
ensure that those Java objects are threadsafe.

But I have now digressed a lot from your original question.

Attila.

What I was suggesting is that your module loading code would look something 
like:

// The engine that you use
ScriptEngine engine = new NashornScriptEngine().getEngine(new String[] { 
"--global-per-engine" });

...

// when loading a module
Bindings moduleVars = new SimpleBindings();
moduleVars.put("require", requireFn);
moduleVars.put("module", moduleDescriptorObj);
moduleVars.put("exports", exportsObj);
Bindings prevBindings = engine.getBindings(ENGINE_SCOPE);
engine.setBindings(moduleVars, ENGINE_SCOPE);
try {
     engine.eval(moduleSource);
} finally {
    engine.setBindings(prevBindings, ENGINE_SCOPE);
}
return exportsObj;

NB: your modules would _not_ run in isolated globals. I thought they do, but I 
just spoke to Sundar and he explained the mechanism to me so now I see they 
won't -- see above the case b).

They could pollute each other's global namespace (since it's shared). Hopefully 
they'd adhere to Modules/Secure recommendation and refrain from doing so, but 
you can't really enforce it.

My require() implementation in Rhino could provide real isolation, but this is 
unfortunately impossible in Nashorn. Nashorn makes an assumption that the 
global object during execution of a script is an instance of 
jdk.nashorn.internals.objects.Global; in Rhino, it could have been anything so 
there I was able to run a module in a new scope that had the actual Global 
object as its prototype, so it could catch all variable assignments in itself 
and essentially make the Global read only (albeit objects in it mutable - e.g. 
a module could still extend Array prototype etc.).

In Nashorn, it's the other way round with "--global-per-engine" - Global object is the 
one immediately visible to scripts, and ENGINE_SCOPE Bindings object is used as source of 
properties that aren't found in the Global. Here, Global catches variable assignments and 
ENGINE_SCOPE Bindings object ends up being immutable (although objects in it are obviously still 
mutable, so the module can build up its "exports" object).

Basically, you have a choice between having shared globals (no isolation) 
without mirrors (and then you won't run into any issues with mirrors), or 
separate globals (with real isolation), but then also mirrors, and then you 
might run into limitations of mirrors (e.g. they can't be automatically used as 
Runnable etc. callbacks from Java and so on.)

I'm not trying to justify or otherwise qualify any of the design decisions 
here, just trying to help you understand its constraints.

Attila.

Reply via email to