On 12/12/13 08:49, Attila Szegedi wrote:
On Dec 12, 2013, at 8:00 AM, Tim Fox <[email protected]
<mailto:[email protected]>> wrote:
On 11/12/13 12:53, Attila Szegedi wrote:
On Dec 11, 2013, at 1:13 PM, Tim Fox <[email protected]
<mailto:[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.
Regarding thread safety... Unlike node, Vert.x is multi-threaded,
however we guarantee that code in a particular Verticle is never
executed concurrently by more than one thread.
In other words - we have a single engine which might contain many
scripts, and may have many threads executing different scripts in it at
any one time, but we guarantee that any particular script is never
executed concurrently by more than one thread. So we don't need any
multithreaded JS semantics.
This works well with Rhino and DynJS. So I assume it's ok for a single
engine to be executed concurrently as long as those threads don't bump
into each other, i.e. don't try and execute the same code concurrently?
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.