Wednesday, January 12, 2011

Embedding API refinements for JRuby 1.6.0RC1

TThe biggest release ever --- JRuby 1.6.0RC1 has been released on Jan. 10, 2011. Headius wrote a blog about this biggest-ever release, JRuby 1.6.0 RC1 Released. Ruby 1.9 compatibility, performance improvement, built-in graph profiler, and more. How about embedding API (RedBridge)? In the release note (http://jruby.org/2011/01/10/jruby-1-6-0-rc1.html), you'll find a line mentioned about RedBridge, "Embedding API refinements." I'm going to write what refinements RedBridge had in JRuby 1.6.0RC1 in this blog post.


1. New Context Instance Type, concurrent

So far, RedBridge has had three types of context models, singleton, singlethread, and threadsafe. JRuby 1.6.0 added one more type, concurrent. The concurrent type is somehow mixture of singleton and threadsafe. Ruby runtime is a singleton, variables (except global variables) are thread local. This model would be good to use RedBridge on servlet based web applications. For example, we can instantiate Ruby runtime and evaluate all Ruby codes during servlet’s initialization, then use them in HTTP request processing methods, such as doGet() or doPost(). Threadsafe context model is similar, but Ruby runtimes are on each thread (each HTTP request on a web app). On the other hand, the runtime in the same classloader will be used on the concurrent context model on all threads, if Ruby runtime has been already initialized by another app.

The concurrent model is a bit complicated and assuming users have enough knowledge about Java web application and Ruby's variable/constant scope. Global variables are totally unprotected from concurrent processing since Ruby runtime is a singleton. As you may know the scope of a global variable is global on Ruby runtime, so is the concurrent context. Global variables of concurrent model are looked up from all threads (all HTTP requests on a web app) simultaneously. The scope of other variable types and a constant depends on how those are used. If those are top-level variables/constants, the scope will be global since Runtime is a singleton. If not, those are thread local and protected from concurrent processing.

When you use the concurrent context model, specify as in below:

Embed core
ScriptingContainer container = new ScriptingContainer(LocalContextScope.CONCURRENT);

JSR223
System.property(“org.jruby.embed.localcontext.scope”, “concurrent”);



2. Receiver aware sharing variables

I’ve written about this new feature in the blog post, http://yokolet.blogspot.com/2010/09/new-featues-of-embedding-api-for-jruby.html ScriptingContainer.get()/put()/remove() methods had a receiver object in those method arguments. If the receiver object is not specified, runtime top-self is used as a receiver. This means those operations are not thread safe on concurrent context type. Be careful.
Also, see the lazy variable retrieval feature of the same blog post. This is for performance improvement.


3. Classloader option for JSR223

The new option to set classloader into ScriptingContainer via ScriptEngineFactory has been added from JRuby 1.6.0RC1. This is for users who want to use JSR223 ScriptEngine for JRuby on some custom classloader. Setting a classloader of ScriptingContainer itself to Ruby runtime is for now well-known technique on custom classloader. The problem was JSR223 doesn’t have a method for that. So, JRuby 1.6.0 sets the classloader that is used to instantiate ScriptingContainer to Ruby runtime by default. This is done in ScriptEngineFactory#getScriptEngine() method internally. This behavior can be disabled by a system property, org.jruby.embed.classloader.

System.setProperty(“org.jruby.embed.classloader”, “current”); // default, sets classloader
System.setProperty(“org.jruby.embed.classloader”, “none”); // doesn't set classloader


The option is JSR223 only. The org.jruby.embed.classloader system property has no effect when you directly instantiate ScriptingContainer. Set whatever appropriate classloader using ScriptingContainer.setClassLoader() method.


4. New Map proxy

This is not a embedding API but JRuby core. Also the feature is not only for RedBridge users but also for all JRuby users. However, the new proxy would make RedBridge users happy especially, so I’ll add this feature here.

Since JRuby 1.6.0RC1, a java.util.Map type object has all Ruby Hash methods by default. Let’s see example code.

ScriptingContainer container = new ScriptingContainer(LocalContextScope.SINGLETHREAD);
ConcurrentHashMap map1 = new ConcurrentHashMap();
map1.put("a", 100);
map1.put("b", 200);
Map map2 = new HashMap();
map2.put("b", 254);
map2.put("c", 300);
container.put("map1", map1);
container.put("map2", map2);
String script =
"map1.merge!(map2) {|k,o,n| o+n }\n" +
"puts \"#{map1.inspect}, length = #{map1.length}\"\n" +
"puts \"class: #{map1.class}\"";
container.runScriptlet(script);

Set entries = map1.entrySet();
for (Map.Entry entry : entries) {
System.out.print(entry.getKey() + ": " + entry.getValue() + ", ");
}

Above code prints:

{"a"=>100, "b"=>454, "c"=>300}, length = 3
class: Java::JavaUtilConcurrent::ConcurrentHashMap
a: 100, b: 454, c: 300,

Two Map type objects were merged as instructed, map1.merge!(map2) {|k,o,n| o+n }. Also in Java world, map1 was changed exactly the same way as was in Ruby world.


These are the brief summary of embedding API refinement done in JRuby 1.6.0RC1. Have fun and please report if you find a bug or encounter a weird behavior.

2 comments:

Adam Murray said...

Thanks for the notes, Yoko.

Concurrent context type is interesting. It might be a good fit for my environment, so I'll experiment with it (and of course file bugs if I run into problems).

yokolet said...

My pleasure, Adam.

I think "concurrent" works for people who feel "threadsafe is somewhat broken." :)