Reusing compiled JS code keeping speed and thread-safety
Benjamin Sieffert
benjamin.sieffert at metrigo.de
Thu Oct 30 16:33:07 UTC 2014
Hello again,
if they are eval'ed in the same engine, they will have the library in
scope, yes. This is exactly why transferring contexts does not work: All
the objects keep working with the context they were created in.
Example:
engine.eval("function add(a, b) { return a + b; }");
ScriptObjectMirror m = engine.eval("obj = { twice: function(n) { return
add(n, n); } }");
m.callMember("twice", 2); // returns 4
You can find the documentation for load with the nashorn docs. It's just a
global function that takes an object with fields "name" and "script". It
will load (eval) the script and scope its stack traces under the given name.
String lib = "function add(a, b) { return a + b; }; \n function broken() {
throw 'error!'; };";
engine.put("library", lib);
engine.eval("load( { 'name' : 'my library', script: library });");
engine.eval("broken()"); // ScriptException: error! in my library at line
number 2 at column number 21
(Putting the String into the engine as a variable is just for readability.
You could just inline the code into the call to load. Remember to escape it
properly.)
Glad if I can help. :)
On 30 October 2014 16:23, yikes aroni <yikesaroni at gmail.com> wrote:
> Hi Benjamin -- thanks for your response. Interesting approach tho not sure
> if it'll work for my situation. A couple of questions:
>
> > *Any one of these scripts gets evaluated by the engine once and the
> returned ScriptObjectMirror*
> Do these ScriptObjectMirror objects have your library in scope somehow?
> That is, if you load library into the engine, then eval() a secondary
> script to get a ScriptObjectMirror, how can you call a function on it via
> callMember() if that function leverages functions/obj/props of the library?
>
> > *To get nice stack traces, we also wrap each small javascript into a
> load()-call in the engine and name it. The library is also wrapped in a
> load()-call.*
> Not clear to me. Any chance you could share a code snippet?
>
> thanks for your time and thoughts!
>
>
> On Thu, Oct 30, 2014 at 10:03 AM, Benjamin Sieffert <
> benjamin.sieffert at metrigo.de> wrote:
>
>> Hello there,
>>
>> we had a similar aim / similar issues with nashorn. Since I don't know
>> your code exactly, I'll just describe our case / what we ended up doing and
>> maybe you can get some ideas off that.
>>
>> We use one ScriptEngine. Among all our threads, just one engine. On
>> start-up we let it compile the library once. The library has no mutable
>> state and is not dependant on global variables. It's just a collection of
>> functions / classes.
>> Then we have a few hundred small javascripts that basically instantiate a
>> class defined by the library and do some configurations on that instance.
>> Any one of these scripts gets evaluated by the engine once and the returned
>> ScriptObjectMirror (the configured javascript object) is then kept on the
>> java side. As this ScriptObjectMirror also does not have any mutable state,
>> we can now safely use it among multiple threads via callMember().
>> To get nice stack traces, we also wrap each small javascript into a
>> load()-call in the engine and name it. The library is also wrapped in a
>> load()-call.
>>
>> All approaches including some sort of mutable javascript-side state sadly
>> do not seem to be workable. The nashorn devs have (understandably)
>> explicitly stated that since javascript does not define multithreaded
>> semantics, they will not be implementing any.
>> Everything evaluated in a NashornScriptEngine keeps its original
>> context/global, even when seemingly "put" into another engine. You cannot
>> prevent pollution. I tried for some time. : )
>>
>> Regards
>>
>> On 30 October 2014 14:09, yikes aroni <yikesaroni at gmail.com> wrote:
>>
>>> Hi ... I’m using nashorn by embedding it in Java (not via command-line).
>>> I
>>> can make it work, but I have a two primary challenges: 1) speed of
>>> execution; and 2) thread safety.
>>>
>>>
>>>
>>> I have a “base” JS library that I compile more or less like this:
>>>
>>>
>>>
>>> ScriptEngineManager *factory* = *new* ScriptEngineManager();
>>>
>>> ScriptEngine *baseEngine* = *factory*.getEngineByName("nashorn");
>>>
>>> Compilable *baseCompilable* = (Compilable) *baseEngine*;
>>>
>>> baseCompilable.compile(*new* java.io.FileReader(*pathDsBase*)).eval();
>>>
>>>
>>>
>>> where *pathDsBase* is a file system path to my javascript library. This
>>> library isn’t huge, but it takes about 500+ ms to compile and eval().
>>> That’s a long time for my purposes, so I want to do it only once.
>>>
>>>
>>>
>>> Other parts of my code then launch multiple Java threads that do various
>>> work and occasionally invoke small, “temporary” Javascript scripts that
>>> need to reference objects and functions in this “base” library. Since I
>>> want these small scripts to run fast and thread-safe, I **could** simply
>>> create a brand new ScriptEngine every time I want to run one of these
>>> small, “temporary” scripts and recompile the base library into it.
>>> Problem
>>> with that is that it takes too much time.
>>>
>>>
>>>
>>> I’ve tried a lot of approaches -- none have worked.
>>>
>>>
>>>
>>> My main approach (which has failed miserably at every turn) is to create
>>> a
>>> “base” ScriptEngine, compile the “base” library into it, then create a
>>> “temporary” ScriptEngine and attempt to set the base bindings / context
>>> to
>>> it so that the temp ScriptEngine only eval()s the “temp” script, but has
>>> the base library in scope. For example something like this test code:
>>>
>>>
>>>
>>> ScriptEngine tempEngine = engineFactory.getScriptEngine(); // a
>>> temporary engine. This is the one that will evaluate the “small” scripts
>>> that reference the “base” script library.
>>>
>>> ScriptContext ctxBase = *new* SimpleScriptContext();
>>>
>>> ctxBase.setBindings(baseEngine.createBindings(), ScriptContext.
>>> *GLOBAL_SCOPE*);
>>>
>>> // evaluate a script at add it to base’s context. This is where
>>> the
>>> “base” script libraries would be eval()-ed into the baseEngine context.
>>>
>>> baseEngine.eval("var y = 'hello';", ctxBase);
>>>
>>>
>>>
>>> // Now attempt to set the baseEngine’s bindings (which I
>>> assume have y = “hello” in them (since I’m not clear where they end up,
>>> I’ve tried every possible combination or ENGINE and GLOBAL scope.)
>>>
>>> ScriptContext ctxTemp = *new* SimpleScriptContext();
>>>
>>> ctxTemp.setBindings(baseEngine.createBindings(), ScriptContext.
>>> *ENGINE_SCOPE*);
>>>
>>>
>>>
>>> // Now for the temporary, “small” script. The print references a
>>> variable (y) that I had hoped would be set to the temp engine’s scope
>>> from
>>> the “base” engine’s context.
>>>
>>> String script = "var x = 'world'; print(y + x);";
>>>
>>> tempEngine.eval(script, ctxTemp);
>>>
>>>
>>>
>>> Exception is Exception in thread "main" *javax.script.ScriptException*:
>>> ReferenceError: "y" is not defined in <eval> at line number 1
>>>
>>>
>>>
>>> I’ve also tried using loadWithNewGlobal, which solves the threading
>>> issue,
>>> but doesn’t appear to solve the speed issue: the script has to be
>>> recompiled every time.
>>>
>>>
>>>
>>> I am sure I am barking up the wrong tree. Can somebody help point me in
>>> the
>>> right direction for how to do this? Again, my two primary questions are:
>>>
>>>
>>>
>>> 1) (Speed) Generally, how do I make it so that I don’t have to re-eval()
>>> a
>>> large library every time I want to run a script that references it? Or,
>>> more specifically, how can I reuse code compiled (eval()ed) by one engine
>>> (“base”) in another engine (“temp”)?
>>>
>>> 2) (Thread-safety) How do I reuse such a library in a way that running
>>> the
>>> subsequent “temp” scripts doesn’t pollute the “base” bindings that I’m
>>> reusing?
>>>
>>>
>>>
>>> I will be overjoyed if there is a clean solution to this. If not, I will
>>> still be happy to clear up this mystery with a “can’t do that...” if that
>>> is, in fact, the answer....
>>>
>>>
>>>
>>> thanks
>>>
>>
>>
>>
>> --
>> Benjamin Sieffert
>> metrigo GmbH
>> Sternstr. 106
>> 20357 Hamburg
>>
>> Geschäftsführer: Christian Müller, Tobias Schlottke, Philipp Westermeyer,
>> Martin Rieß
>> Die Gesellschaft ist eingetragen beim Registergericht Hamburg
>> Nr. HRB 120447.
>>
>
>
--
Benjamin Sieffert
metrigo GmbH
Sternstr. 106
20357 Hamburg
Geschäftsführer: Christian Müller, Tobias Schlottke, Philipp Westermeyer,
Martin Rieß
Die Gesellschaft ist eingetragen beim Registergericht Hamburg
Nr. HRB 120447.
More information about the nashorn-dev
mailing list