Should I use a separate ScriptEngine and CompiledScript instances per each thread?
Asked Answered
A

5

55

My program uses Java Scripting API and can eval some scripts concurrently. They don't use shared script objects, Bindings or Context, but can use same ScriptEngine and CompiledScript objects. I see that Oracle Nashorn implementation in Java 8 is not multithreaded, ScriptEngineFactory.getParameter('THREADING') returns null about which the documentation says:

The engine implementation is not thread safe, and cannot be used to execute scripts concurrently on multiple threads.

Does it mean that I should create a separate instance of ScriptEngine for each thread? Besides, documentation says nothing about CompiledScript concurrent usage but:

Each CompiledScript is associated with a ScriptEngine

It can be assumed that CompiledScript thread-safety depends on related ScriptEngine, i.e. I should use separate CompiledScript instance for each thread with Nashorn.

If I should, what is the appropriate solution for this (I think very common) case, using ThreadLocal, a pool or something else?

final String script = "...";
final CompiledScript compiled = ((Compilable)scriptEngine).compile(script);
for (int i=0; i<50; i++) {
    Thread thread = new Thread () {
        public void run() {
            try {
                scriptEngine.eval(script, new SimpleBindings ());  //is this code thread-safe?
                compiled.eval(new SimpleBindings ());  //and this?
            }
            catch (Exception e)  {  throw new RuntimeException (e);  }
        }
    };
    threads.start();
}
Agriculture answered 9/5, 2015 at 12:47 Comment(0)
F
63

You can share a ScriptEngine and CompiledScript objects across threads. They are threadsafe. Actually, you should share them, as a single engine instance is a holder for a class cache and for JavaScript objects' hidden classes, so by having only one you cut down on repeated compilation.

What you can't share is Bindings objects. The bindings object basically corresponds to the JavaScript runtime environment's Global object. The engine starts with a default bindings instance, but if you use it in multithreaded environment, you need to use engine.createBindings() to obtain a separate Bindings object for every thread -- its own global, and evaluate the compiled scripts into it. That way you'll set up isolated global scopes with the same code. (Of course, you can also pool them, or synchronize on 'em, just make sure there's never more than one thread working in one bindings instance). Once you evaluated the script into the bindings, you can subsequently efficiently invoke functions it defined with ((JSObject)bindings.get(fnName).call(this, args...)

If you must share state across threads, then at least try to make it not mutable. If your objects are immutable, you might as well evaluate the script into single Bindings instance and then just use that across threads (invoking hopefully side-effect free functions). If it is mutable, you'll have to synchronize; either the whole bindings, or you can also use the var syncFn = Java.synchronized(fn, lockObj) Nashorn-specific JS API to obtain versions of JS functions that synchronize on a specific object.

This presupposes that you share single bindings across threads. If you want to have multiple bindings share a subset of objects (e.g. by putting the same object into multiple bindings), again, you'll have to somehow deal with ensuring that access to shared objects is thread safe yourself.

As for THREADING parameter returning null: yeah, initially we planned on not making the engine threadsafe (saying that the language itself isn't threadsafe) so we chose the null value. We might need to re-evaluate that now, as in the meantime we did make it so that engine instances are threadsafe, just the global scope (bindings) isn't (and never will be, because of JavaScript language semantics.)

Forebrain answered 11/5, 2015 at 3:54 Comment(8)
The only question I have is concerning the cast of the bindings object to JSObject. Eclipse complains about this: restriction on required library '/Library/Java/JavaVirtualMachines/jdk1.8.0_71.jdk/Contents/Home/jre/lib/ext/nashorn.jar'). Why is this?Fluency
Are there any code samples that show how to correctly use Nashorn in a multithreaded environments? Also, I cannot find newBindings() method on ScriptEngine - should it be createBindings()?Milne
@Milne Did you have any luck in finding some code samples or where the newBindings is?Demakis
@Demakis No, after some initial research, I decided against using Nashorn in the project so I stopped investigating it further.Milne
Faced serious crosstalk issues while using One JSEngine to service all the threads. Though the bindings were copied ( using putAll ) before each execution, a JMeter test proved fatal to the overall outcome. Ended up doing threadlocal engines instead.Rusty
I have tried using Non-compiled (just java strings as scripts) scripts and so far it looks like there is no issue. There is no crosstalk. Anyone tried this?Facture
@NishM, It seems NashornScriptEngine can't execute scripts concurrently even with different ScriptContext, but it is fine to service threads in sequence. So a better solution than ThreadLocal engine is to use a pool of engine to service all the threads. An easy way is to use Apache Common Pool.Snowdrop
One engine per thread is a difficult pattern to follow when using CompiledScript, as the eval is off of the script object which stored the engine within it. I have had no cross-talk issues using a single engine to compile all scripts, and then executing CompiledScript.eval(ScriptContext) simultaneously across multiple threads, where each ScriptContext is short-lived and confined to a single thread. All my efforts to improve efficiency by creating ScriptContexts from existing Bindings or Contexts have failed, so I re-evaluate my included JS library with every new ScriptContext.Issiah
L
3

ScriptEngine for Nashorn is not thread-safe. This can be verified by calling the ScriptEngineFactory.getParameter("THREADING") of the ScriptEngineFactory for Nashorn.

The value returned is null which according to java doc means not thread safe.

Note: This part of the answer was first given here. But I rechecked the results and doc myself.

This gives us the answer for CompiledScript as well. According to java doc a CompiledScript is associated to one ScriptEngine.

So in Nashorn ScriptEngine and CompiledScript should not be used by two threads at the same time.

Lubow answered 27/6, 2015 at 17:38 Comment(1)
This contradicts Attila's more comprehensive answer. Given his background and the context provided I believe it is the correct one.Nagari
A
1

The accepted answer will mislead many people.

In short:

  • NashornScriptEngine is NOT thread-safe
  • If you do NOT use global bindings, the state-less part can be thread-safe
Annadiana answered 22/8, 2017 at 9:53 Comment(1)
could you elaborate on "can be"? if the code is just functions (no state), is it then thread-safe, or does more work need to be done?Underline
A
0

Code sample for @attilla's response

  1. my js code is something like this:

    
    var renderServer = function renderServer(server_data) {
       //your js logic...
       return html_string.
    }
    
  2. java code:

    
    public static void main(String[] args) {
            String jsFilePath = jsFilePath();
            String jsonData = jsonData();
    
    
        try (InputStreamReader isr = new InputStreamReader(new URL(jsFilePath).openStream())) {
    
            NashornScriptEngine engine = (NashornScriptEngine) new ScriptEngineManager().getEngineByName("nashorn");
            CompiledScript compiledScript = engine.compile(isr);
            Bindings bindings = engine.createBindings();
    
            compiledScript.eval(bindings);
    
            ScriptObjectMirror renderServer = (ScriptObjectMirror) bindings.get("renderServer");
            String html = (String) renderServer.call(null, jsonData);
            System.out.println(html);
    
       } catch (Exception e) {
           e.printStackTrace();
       }
    }
    

be careful when using renderServer method in multi-threaded environment, since bindings are not thread safe. One solution is to use multiple instances of renderServer with re-usable object pools. I am using org.apache.commons.pool2.impl.SoftReferenceObjectPool, which seems to be performing well for my use case.

Arvad answered 29/6, 2017 at 19:46 Comment(1)
in your js code, if "your js logic" does not have any state, e.g. doesnt refer to any global variables, is it not thread safe? i.e. are pools etc required if there is no state in the js code?Underline
D
0

The result of my attempt is that executing the same String script, ScriptEngine is thread-safe, and thread safety issues arise when the script is different.

Dulse answered 23/8, 2023 at 7:13 Comment(1)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Swee

© 2022 - 2024 — McMap. All rights reserved.