I have a scenario where I am processing user provided templates that contain portions of JavaScript. I am using Java's ScriptEngine
to evaluate those bits of JavaScript. I have various helper functions that I only wish to evaluate once (think of these helpers functions as loading something like Underscore.js
).
Once this has been loaded I then loop through rows of data and process the template for each row - these templates may use any of the previously loaded helper functions. I am trying to process the rows in parallel as Java's ScriptEngine.eval()
is quite slow in this scenario.
Anyone who looks at this over simplified SSCCE will know that I am getting the expected outcome that I have shown, as I am modifying a global object in a non thread safe way. In my example sJsGlobal
is this global JavaScript. The global functions need access to various row specific bits of data, that I place within obj
.
For sJsGlobal
to successfully eval()
it needs to know about obj
or it will fail. In my SSCCE, each thread is updating the same global obj
before calling a function. I am looking for a way for each global function to see a local obj
. It is a Catch-22 situation, I need to define the variable for the global script to know it exists, but I want it to be of local significance.
import java.util.*;
import java.util.concurrent.*;
import javax.script.*;
public class SSCCE {
public static void main(String[] args) throws Exception {
StringBuilder sJsGlobal = new StringBuilder();
sJsGlobal.append("var obj = {};");
sJsGlobal.append("function get_row() { return \"Row is \" + obj.row }");
ScriptEngine jsGlobal = new ScriptEngineManager().getEngineByName("JavaScript");
jsGlobal.eval(sJsGlobal.toString());
Bindings gB = jsGlobal.getBindings(ScriptContext.ENGINE_SCOPE);
ExecutorService es = Executors.newCachedThreadPool();
ArrayList<Future<String>> af = new ArrayList<Future<String>>();
for (int i = 0; i < 10; i++) {
final int fi = i;
af.add(es.submit(() -> {
StringBuilder sJsLocal = new StringBuilder();
sJsLocal.append("obj.row = " + fi + ";");
sJsLocal.append("var x = get_row();");
ScriptEngine jsLocal = new ScriptEngineManager().getEngineByName("JavaScript");
ScriptContext sc = new SimpleScriptContext();
Bindings lB = jsLocal.createBindings();
lB.putAll(gB);
sc.setBindings(lB, ScriptContext.ENGINE_SCOPE);
jsLocal.setContext(sc);
jsLocal.eval(sJsLocal.toString());
return (String)jsLocal.get("x");
}));
}
for (Future<String> f : af) {
System.out.println("Returned -> " + f.get());
}
es.shutdown();
}
}
This results in the following output where the rows are being overwritten as it isn't thread safe due to my use of this global obj
variable. Even with a separate ScriptEngineManager
and ScriptEngine
. I need a way of settings the bindings so they don't interfere with other instances.
Returned -> Row is 4
Returned -> Row is 4
Returned -> Row is 4
Returned -> Row is 4
Returned -> Row is 8
Returned -> Row is 5
Returned -> Row is 6
Returned -> Row is 7
Returned -> Row is 8
Returned -> Row is 9
I could pass the row specific data into each function as variables, but this becomes very messy as my example doesn't do justice to the amount of data I need to exchange. The functions are also called by user scripts which makes this very difficult. I really want to find a way to redefine obj
in each thread before I evaluate sJsLocal
.
This all works fine if I process each row one after another without threading, but I am trying to speed this up a bit.