JRuby?

Poster Content
nk4um Moderator
Posts: 899
October 11, 2011 15:05Success - JRuby 1.6.4 Integrated with NetKernel

Hi Guys,

After some deep research and a few creative workarounds I'e got an integration of JRuby on NetKernel which I'm finally happy with.

Whilst switching to the ScriptingContainer seemed to be a good solution initially - I found that there was a dangerous fundamental core assumption about runtime context variables in the org.jruby.ast.executable.Script. Variable state is tied to ThreadLocals.

This doesn't play well with NetKernel since its very common to have re-entrant extrinsic recursive requests - that is the same thread will often re-enter the Ruby runtime before the initial script evaluation has completed. Its very common to run NetKernel with just one or two threads per CPU core. Requests are logically decoupled from Threads and so the NK kernel will allocate threads to requests so as to load-balance the system across available cores. The net result of this is that its not a safe assumption to think that the ThreadLocal variables are unique to a given Ruby Runtime/Script execution.

Fortunately, we were able to work out a "horse-swapping" pattern - whereby the state that would have been stomped on is extracted, the script then run, and finally the original state reattached. This doesn't seem to affect performance and means we're running concurrently with just a handful of threads and just one Ruby runtime per unique ROC spacial context (typically that's one per application).

The upshot of all this work can be graphically seen in these before and after unit test results...


NK active:ruby tests before



NK active:ruby tests after


As you can see - the total time has come down from around 2 seconds to around 200ms (10x improvement). But these tests are generally testing both the compile time and runtime phases. The second test is more typical of operational load - a call to the runtime with a pre-compiled (transrepted) script. This has gone from 60ms to around 1-2ms.

These levels of performance are in the same ballpark as our other supported languages (groovy, python, javascript, dpml, xslt, xquery, dynamic-java etc) - so I'm happy to think that we've now got a decent solution.

Downloads

Please give these new builds of the libraries a try. As well as a new runtime library, the ruby.lib module now has the latest 1.8 Ruby library from the JRuby1.6.4 distro, and there are some more tests to trap extrinsic recursive Threadlocal mix-ups...

Thanks

Thanks for providing the spur to re-examine this. It will be great to be able to offer Ruby as another language choice on NK. Charles, please convey our thanks to everyone that has contributed to JRuby - when we're clear from the NK5 release process I'll post a few suggestions on the JRuby project issue tracker based on my experiences.

All the best,

Peter

PS Next time I'm in MSP - lets meet up for a beer (likely to be December time frame).

Like · Post Reply
nk4um User
Posts: 2
October 10, 2011 18:45

I'm glad the ScriptingContainer API is working out for you. I would suggest you direct questions about the embedding API to Yoko Harada, the JRuby committer who works on it. I'm sure she can help you tune things to your liking or enhance what we have. You can reach her as @yokolet on Twitter or if you post to the JRuby users list she'll see that too. I assume you've already seen her extensive docs in the wiki.

As for LoadService...there are now at least three projects I know of that extend or replace LoadService. I think we need to come up with a solution that makes it more extensible, but having never attempted to extend or replace it I'm not sure I have a good picture of what that would look like. Perhaps you can open a JRuby issue (http://bugs.jruby.org) for us to discuss an update to LoadService?

Like · Post Reply
nk4um Moderator
Posts: 899
October 10, 2011 15:28Experimental JRuby 1.6.4 Integration

Hi mml / Charles,

OK with the stimulus of your interest and the safety net of having the attention of someone who actually knows what's going on under the hood - I revisited the latest JRuby 1.6.4 build and our experimental integration.

Here is a pretty complete integration, now ported to use the ScriptingContainer...

Story So Far

After digging around I realised that the ScriptingContainer can be shared and using the LocalContextScope.THREADSAFE we can keep the Ruby instances isolated.

FYI the thing that's new and different about ROC/NetKernel is that unlike Unix, for example, the address space from which a script engine is executed is dynamic and multi-dimensional - so its not possible to consistently use a single container. Therefore what we've done is keep an instance of ScriptingContainer for each unique spacial arrangement - hopefully this provides sufficient independence without too large an operational footprint.

We've added a local implementation of org.jruby.runtime.load.LoadService which will try to use the default path algorithm but will also try to use the NK ROC address space as last resort - this means JRuby can load classes from the modular ROC address space. Charles, it would be really nice if LoadService were an interface which we could replace - we implemented and provided a ROCLoadServiceCreator - but it has to be hard coded to the LoadService - which meant we had to override and modify that class (fortunately NK's module classloading allows local classes to mask library classes).

It would have been very cool if we could have set the LoadService (and Path) as a dynamic parameter for the execution context of an individual script - in ROC we regard everything as a resource, so the Path itself should be dynamic. But this is wish-list item - for now we've defaulted to always sourcing res:/etc/RUBYPATH to determine the ROC domain searchable ruby class path.

Current Status

So the upshot is this. Our hello world test has gone from 100ms to about 10ms. (Now we're talking). It seems it will go even faster if we use LocalContextScope.CONCURRENT - however we are not sure that the Ruby.getGlobalVariables() is then independent for each thread. We have to have this since we inject an INKFRequestContext into the global $context object of each script execution so that Ruby can call into the ROC address space. (This is the same pattern and makes the NKF-API available in all the languages we integrate).

Here's some code with inline comments. It would be very helpful to get your input on the questions I've raised.

publicvoid execute(INKFRequestContext aContext, ClassLoader cl, RepRubyPath path, RepRubyScript rrs) throws Exception
    {   //Setup Ruby
        List key=getCacheKey(aContext);
        System.out.println("RUBY SCRIPT CACHE SIZE:" + mScriptContainerCache.size());
        ScriptingContainer sc=mScriptContainerCache.get(key);
        if(sc==null)
        {   sc=new ScriptingContainer(LocalContextScope.THREADSAFE, LocalVariableBehavior.TRANSIENT);
            sc.setCompileMode(RubyInstanceConfig.CompileMode.OFF);
            sc.setClassLoader(cl);
            sc.setClassCache(new ClassCache(cl));
            sc.setLoadServiceCreator(new ROCLoadServiceCreator(aContext, path));
            sc.getProvider().getRuntime().getLoadService().require("java");
            if(mScriptContainerCache.size() >= 50)
            {   clearCache();
            }
            mScriptContainerCache.put(key,sc);
        }
        Ruby ruby=sc.getProvider().getRuntime();
        //Is this the same Instance for all Runtimes?  It seems to be unless we use LocalContextScope.THREADSAFE ? 
        ruby.getGlobalVariables().defineReadonly(   GlobalVariable.variableName("context"),     new ContextGlobalVariable(ruby, aContext));     
        //Executetry
        {   ruby.runScript(rrs.getScript());
        }
        finally
        {   //Should we do this?  Concerned how ScriptingContainer manages these and if we have an open ended memory leak?//ruby.tearDown();
        }
    }

Remaining Problem

Finally - we're not quite clear yet. I've noticed that we're seeing intermittent errors at runtime. It is sporadic and may be concurrency related since it occurs more frequently with our test of one active:ruby call invoking another...

'undefined local variable or method `org' for main:Object'

If you run the unit tests repeatedly you'll see this coming up. The unit tests are not actually running any concurrent requests to the runtime - so its strange that this is occurring.

Note that we have a transreptor which is compiling all scripts - first with a ScriptingContainer.parse() then we compile the Node with the Ruby.tryCompile(). We then treat these compiled scripts as cacheable resources and they are handed to the runtime on each request - we are assuming that a compiled Script is immutable and threadsafe? This may be a dangerous assumption but its a pattern we've managed to pull off with all the other languages we've integrated.

Anyway please give this a try - and if you can help with the open questions I think we're close to having a successfully integration.

Cheers,

Peter

Like · Post Reply
nk4um User
Posts: 2
October 10, 2011 05:09what is it you need?

I am replying on mobile so bear with me.

JRuby has an extensive embedding api that is supports many different embedding models. There is some setup required if you want a pristine environment each time, but it should br nowhere near 100ms per runtime. I have tested repeat boots of JRuby in the same JVM and the startup time is very quick...as quick after a few runtimes as the C impl of ruby. Perhaps something you are doing in NetKernel is causing JRuby's classes to reload repeatedly, and therefore never warm up?

Can you describe what it is that Jython or Groovy are doing that you could not do with JRuby? If anything, JRuby should start up *faster* than both languages.

Like · Post Reply
nk4um User
Posts: 6
October 9, 2011 18:52

i'm guessing the scripting api may be inappropriate for this case. I'm thinking along the lines of nailgun et. al.

Like · Post Reply
nk4um Moderator
Posts: 899
October 9, 2011 17:54

Just glanced at the latest javadocs. As I feared there's still something of a problem. Coming from the Ruby domain, quite naturally there is a very filesystem-centric view of resource loading.

There's a convenient ScriptingContainer...

http://www.jruby.org/apidocs/org/jruby/embed/ScriptingContainer.html

The critical thing is that it shares its resource loading across invocations - this is what you'd expect if you have a filesystem view of resource loading. But this is no good in the stateless ROC domain.

A language runtime in NK is stateless and is shared (as a pseudo-singleton) with every application in the system. Therefore every invocation has a distinct (and possibly unique) ROC resource space through which it has been requested and which is the context in which loading resources and issuing requests is made. (NK systemically takes care of the caching and optimisation of the resources and spaces for you).

I'll find some time to look inside the Impl and see if things have changed since I last took it all apart.

P.

Like · Post Reply
nk4um Moderator
Posts: 899
October 9, 2011 17:34

That's great. FYI the integration we have is solid - in the sense that active:ruby behaves just like any other language in NK and can access its libraries in the ROC domain.

Our issues are probably down to a historical lack of an integration API and/or lack of knowledge of how to optimize the internal initialization. (Note its not sufficient to have a Java ScriptEngine interface - since Java's scripting language support is very basic and doesn't give the control we need to persuade a language to use the ROC domain, use the NK classloaders and provide a transreption model for JIT bytecode compilation and caching).

Also it would be nice to be able to abstract and supply something like a URIResolver to the library loader - at the moment we've had to hack in a back-door route to get JRuby to look in the ROC space for its Ruby class resources. As I recall we also had to work out a way to make it accept and use the NK module's SuperStackClassLoader (for Java classes).

Anyway kick the tyres and tell us if it fits the bill and no doubt we can talk with the JRuby team and find a more optimal integration.

P.

Like · Post Reply
nk4um User
Posts: 6
October 9, 2011 17:28

I wouldn't expect the instantiation and initialization of the jruby runtime to be fast in any way.

I think the general model is to keep a pool of runtimes around, and feed them scripts, which should be reasonably speedy. JRuby code actually get speedier as more and more of the code is JIT'd, and hot-spotted down to native code.

There's also a jruby-> bytecode compiler available, though i've never found a reason to use it.

anwyay, thanks for the code. I'll keep you posted.

Like · Post Reply
nk4um User
Posts: 6
October 9, 2011 17:18

As it happens, I'm in Minneapolis, and happen to know Charlie, Tom, Nick, and have forwarded your response to the original query to them.

I know that JRuby has been working on their embedding api, so perhaps things have changed.

I'm not entirely sure why it would matter whether a language is compiled or purely interpreted, as a functional abstraction of an execution service. In any event, i'm pretty sure the JRuby team will have some interesting insight into the matter.

You may also consider looking into mirah. It turns a pretty cool jvm language (with type inference, blocks, the usual modern goodies) into java, whence it can be turned into bytecode. mirah lives here

Like · Post Reply
nk4um Moderator
Posts: 899
October 9, 2011 17:18JRuby Modules - Status Experimental

Here are the modules to play with...

To install just download these files and add an entry pointing to each one in NK's /etc/modules.xml

After install go here to run the unit tests...

http://localhost:1060/test/exec/html/urn:test:org:ten60:netkernel:lang:ruby

To see what we mean about the set-up time of the language being (to us) unacceptable look at the first unit test - its taking about 100ms on my machine to do "Hello World". This is completely dominated by the internal setting up of the Java bindings inside the ruby engine. By contrast something like Jython has access to Java too and will compile to bytecode and runs sub-millisecond.

Let us know how you get on...

P.

Like · Post Reply
nk4um Moderator
Posts: 899
October 9, 2011 17:08JRuby Status

A few people have also recently asked if they could have the active:ruby language runtime back. As Tony said, we haven't shipped it as an officially supported module for NK4 as we're somewhat disappointed with how JRuby operates as an embedded language in the JVM - as far as we've been able to determine it lacks a compile/execute context separation such as you see in Groovy and Rhino Javascript. Even Jython, which is a similar Unix-heritage language, is also much simpler to integrate.

We'll post the module and unit tests for you so you can see what you think. We'll also take a look at the latest state-of-the-art JRuby version - I think its been about 5 months since I looked over there.

Also I have it as an action item, the next time I'm in the Minneapolis area to arrange to meet up with the core JRuby team (I recently discovered they're based in the MSP area when I gave a presentation at the University there).

FYI in principle there is no fundamental issue with Ruby on NK. Just as with our integration of Jython, we have made the RubyPath a set of resource paths in the ROC address space. Therefore to Ruby (as with Python already) NK looks identical to a Unix host and even supports loading classes from the standard Ruby class library mounted in a module.

Peter

PS I'll dig out the modules and post something in a few minutes.

Like · Post Reply
nk4um User
Posts: 6
October 9, 2011 16:31

I'd love to see that module. Not having jruby support is a bit of a showstopper for us.

Like · Post Reply
nk4um Moderator
Posts: 599
October 8, 2011 14:20No supported module for JRuby

Hi, we never really had a lot of success with getting a quality integration of jruby. It has model which requires it to be started from the ground up for each execution which makes it ok for command line execution but a bit heavyweight for an endpoint. The result of this is that it ran really slow.

We do have an experimental module for NetKernel 4 but we've not had opportunity to see if they've moved on. We did that that work in 2009. I'd be happy to send you the module we have for you to have a play with though if you like.

Cheers, Tony

Like · Post Reply
nk4um User
Posts: 6
October 7, 2011 20:52JRuby?

It seems jruby support was included in nk 3.* series, but I can't seem to find any reference to it in the 4 series. please advise.

Like · Post Reply