Skip to main content

Concurrent use of embedded Ruby in Java (using JRuby)

Posted by boneill42 on November 30, 2011 at 11:28 AM PST

Last night, I was finishing up the map/reduce capabilities within Virgil. We hope to allow people to post ruby scripts that will then get executed over a column family in Cassandra using map/reduce. To do that, we needed concurrent use of a ScriptEngine that could evaluate the ruby script. In the below code snippets, script is a String that contains the contents of a ruby file with a method definition for foo.

First, I started with JSR 223 and the ScriptEngine with the following code:

public static final ScriptEngine ENGINE = new ScriptEngineManager().getEngineByName("jruby");

ScriptContext context = new SimpleScriptContext();

Bindings bindings = context.getBindings(ScriptContext.ENGINE_SCOPE);

bindings.put("variable", "value");

ENGINE.eval(script, context);

That worked fine in unit testing, but when used within map/reduce I encountered a dead-lock of sorts. After some googling, I landed in the Redbridge documentation. There I found that jruby exposes a lower-level API (beneath JSR223) that exposes concurrent processing features. I swapped the above code, for the following:

this.rubyContainer = new ScriptingContainer(LocalContextScope.CONCURRENT);

this.rubyReceiver = rubyContainer.runScriptlet(script);

container.callMethod(rubyReceiver, "foo", "value");

That let me leverage a single engine for multiple concurrent invocations of the method foo, which is defined in the ruby script.

This worked like a charm.


Related Topics >>

There is a way how to do it in pure JDK manner without ...

There is a way how to do it in pure JDK manner without resorting to specific-language API. The problem is that you should not take the bindings, but create new ones. This code demoes the problem. I used Groovy, but if you redo the script, you can use Ruby or built-in ecmascript. First it uses getBindings and sharing of the value between scripts in different threads is clearly visible (you may need to adjust the sleep, before running the second thread so the first has time to print the first statement). Second version uses createBindings and these are provided as a parameter to eval - and everything works OK.

import javax.script.Bindings;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class ScriptBindingsConcurrency {

public static final String SCRIPT = "def tname = Thread.currentThread().getName();\n" +
"println "$tname: $i"; sleep(3000); println "$tname: $i";";

private static ScriptEngine scriptEngine;

public static void main(String[] args) throws ScriptException, InterruptedException {
scriptEngine = new ScriptEngineManager().getEngineByName("groovy");

System.out.println("Using created bindings");

private static void usingEngineScopedBindings() throws InterruptedException {
Bindings bindings = scriptEngine.getBindings(ScriptContext.ENGINE_SCOPE);
bindings.put("i", 10);
new Thread(() -> runScript(bindings)).start();
System.out.println("Setting 20");
bindings.put("i", 20);
Thread thread = new Thread(() -> runScript(bindings));

private static void usingCreatedBindings() throws InterruptedException {
Bindings bindings = scriptEngine.createBindings();
bindings.put("i", 10);
new Thread(() -> runScript(bindings)).start();

System.out.println("Setting 20");
Bindings bindings2 = scriptEngine.createBindings();
bindings2.put("i", 20);
new Thread(() -> runScript(bindings2)).start();

private static void runScript(Bindings bindings) {
try {
scriptEngine.eval(SCRIPT, bindings);
} catch (ScriptException e) {