Performing a function in a specific context in Nashorn

I have a regular Nashorn runtime environment that I configured using some global functions and objects - some of them are stateless and some of them are states. Against this runtime, I run some user scripts.

For each run, I plan to create a new context supported by the global context:

myContext.setBindings(engine.createBindings(), ScriptContext.ENGINE_SCOPE); engine.eval(myScript, myContext); 

Based on what I read, any changes in the global scope (in terms of the script) will be limited to the new context I created.

When evaluating these scenarios, they expose some objects (with well-defined names and method names). I can call the method on the object, dropping the engine in Invocable . But how do you know the context in which this function will work? Is this even a problem, or is the execution context of this function configured based on the context in which it was evaluated?

What behavior can be expected in a multithreaded situation, when all threads share the same instance of script -engine, and they all try to run the same script (which provides a global object). When I then call the method on the object, in what context will the function execute? How does he know which instance of the object to use?

I expected to see the invoke method, where I can specify the context, but this does not seem to be the case. Is there a way to do this, or am I completely wrong about this?

I know that an easy way around this is to create a new instance of script -engine for each execution, but as I understand it, I would lose the optimization (especially in the general code). As the saying goes, will there be precompilation here?

+5
source share
1 answer

I get it. The problem I ran into was that invokeFunction would throw a NoSuchMethodException because the functions exposed by the user script did not exist in the bindings from the default scope:

 ScriptContext context = new SimpleScriptContext(); context.setBindings(nashorn.createBindings(), ScriptContext.ENGINE_SCOPE); engine.eval(customScriptSource, context); ((Invocable) engine).invokeFunction(name, args); //<- NoSuchMethodException thrown 

So, I needed to get the function out of context by name and call it explicitly like this:

 JSObject function = (JSObject) context.getAttribute(name, ScriptContext.ENGINE_SCOPE); function.call(null, args); //call to JSObject#isFunction omitted brevity 

This will call a function that exists in your newly created context. You can also call methods on objects this way:

 JSObject object = (JSObject) context.getAttribute(name, ScriptContext.ENGINE_SCOPE); JSObject method = (JSObject) object.getMember(name); method.call(object, args); 

call throws an exception (a Throwable exception wrapped in a RuntimeException or NashornException that was NashornException using the JavaScript stack stack information), so you may need to explicitly handle this if you want to provide useful feedback.

Thus, threads cannot step over each other because each thread has a separate context. I was also able to share custom runtime between threads and make sure that state changes in mutable objects subjected to custom execution were isolated by context.

To do this, I create an instance of CompiledScript that contains a compiled view of my custom runtime library:

 public class Runtime { private ScriptEngine engine; private CompiledScript compiledRuntime; public Runtime() { engine = new NashornScriptEngineFactory().getScriptEngine("-strict"); String source = new Scanner( this.getClass().getClassLoader().getResourceAsStream("runtime/runtime.js") ).useDelimiter("\\Z").next(); try { compiledRuntime = ((Compilable) engine).compile(source); } catch(ScriptException e) { ... } } ... } 

Then, when I need to execute the script, I evaluate the compiled source, and then evaluate the script in this context too:

 ScriptContext context = new SimpleScriptContext(); context.setBindings(engine.createBindings(), ScriptContext.ENGINE_SCOPE); //Exception handling omitted for brevity //Evaluate the compiled runtime in our new context compiledRuntime.eval(context); //Evaluate the source in the same context engine.eval(source, context); //Call a function JSObject jsObject = (JSObject) context.getAttribute(function, ScriptContext.ENGINE_SCOPE); jsObject.call(null, args); 

I checked this with multiple threads, and I was able to verify that state changes were limited by contexts belonging to individual threads. This is because the compiled view is executed in a specific context, which means that the instances of something that it displays are tied to that context.

One of the small drawbacks is that you can unjustifiably overestimate the definitions of objects for objects that do not need a stream-dependent state. To get around this, evaluate them directly on the engine, which will add the bindings for these objects to the ENGINE_SCOPE engine:

 public Runtime() { ... String shared = new Scanner( this.getClass().getClassLoader().getResourceAsStream("runtime/shared.js") ).useDelimiter("\\Z").next(); try { ... nashorn.eval(shared); ... } catch(ScriptException e) { ... } } 

Then later, you can populate the thread- ENGINE_SCOPE context with the ENGINE_SCOPE engine:

 context.getBindings(ScriptContext.ENGINE_SCOPE).putAll(engine.getBindings(ScriptContext.ENGINE_SCOPE)); 

One thing you will need to do is make sure that all such objects that you have opened have been frozen. Otherwise, you can override or add properties to them.

+9
source

Source: https://habr.com/ru/post/1235580/


All Articles