Thursday, April 13, 2006

The Beginning of JRuby on Rails

It must be something of a debate in the blogosphere as to whether titles should be descriptive, possibly giving away a great secret hidden within an entry's text, or whether titles should only hint at the truth, enticing a curious and diligent reader to venture onward. The former perhaps produces a better tagline for newsfeeds, where a less-descriptive title may not be as attention grabbing. The latter is certainly more suspenseful, allowing a careful author to draw readers toward a mounting climax. I prefer the former, and so this entry's title once again gives away the Big Secret:

Today, Rails handled a simple request under JRuby.

Now perhaps this monumental event deserves uproarious fanfare, and perhaps it does not. I will let you be the judge.

We really WERE close!

For several weeks now, Tom and I have been saying back and forth to each other that we thought Rails was very close to working. Developer optimism, perhaps, but we've poured a lot of effort into fixing the last interpreter issues preventing Rails from running successfully. In the process, we've accomplished many peripheral goals like getting IRB running, improving RubyGems support, reparing untold interpreter and core class bugs, and getting a round education in the internals of Rails. I've personally traced through more Rails code than I ever hoped to, and committed patches upon patches to achieve success. Tom has also been cranking out fixes, and our growing community of contributors have sent some amazing enhancements our way. In short, there's real momentum behind JRuby right now, and we had a feeling Rails was almost there.

The last time I really burned the midnight oil was probably over a week ago. With SourceForge CVS down for an entire weekend, family business to attend to, and a pre-JavaOne presentation at the Minnesota Object Technology Users Group's Java Special Interest Group (whew!), there hasn't been a lot of time for late-night coding jags. That changed today.

With all other distractions behind me (the Java SIG presentation was last night), I set out today to finally accomplish our biggest goal

I resolved that by night's end, Rails on JRuby would handle a request.

Expecting a long night, I put on some music (somafm.com is excellent...listen and donate), grabbed a few snacks and beverages, and dug in.

How did I do it?

Now I know that should probably say how did "we" do it. Tom has obviously put in a substantial amount of time on JRuby, and our contributors deserve their props. My intent here is just to describe the final steps leading up to Rails working, to allow you to better judge our success and to document steps necessary for others to replicate my work.

Step 1: Apply all outstanding patches

Over the past several weeks, a number of core bugs have been resolved. In many cases, we had not yet committed those changes to CVS, preferring to review and clean them up. I committed several such fixes this evening:

- A patch to allow Kernel#system and backquote calls to stay in-process if executing a Ruby script. Previously, this caused a new interpreter to be launched (in a new JVM), which was obviously a bad thing for a Java app to do. This fix is hopefully a temporary modification, pending a better solution in RCR 328.
- A rejiggering of the Main and CommandlineParser classes in JRuby to better allow Multi-VM support in the future. These two classes originally used System.x input and output streams and called System.exit on error. For an interpreter that's intended to run in controlled environments, these obviously had to be fixed. The new versions won't kill off a VM and will only use the streams provided to them.
- A small fix for String#split to allow for splitting on ? characters (kinda important for URLs, no?). I have an improved fix for split to be committed tomorrow.
- A new ENV implementation that takes advantage of Java5's new System.getenv method, and falls back to other hacks on earlier Java versions. Originally, without any way to retrieve env vars, JRuby had an empty ENV hash. This fix was necessary in order to run Rails in my preferred test configuration. (Can you guess why?)
- Process#times was not implemented (because it can't be supported under Java). I added it to return a Tms with all zeros.
- File#flock was not implemented. I added it, using java.nio file locking support.

A few of these fixes came out of my playing with Rails this evening, and I stopped when Rails started working.

Step 2: Set up Rails in the most traditional way

Rails is a very CGI-style web framework. In its simplest form, it is a basic CGI script, and all hacks to improve its performance build around that idea. Given that Rails wanted to be CGI, it made sense to set it up that way...with JRuby executing it.

dispatch.cgi is the main CGI script for Rails. There's also a .fcgi version for FastCGI. Eventually, JRuby will provide a "CGI servlet" of some kind to wrap Rails requests, as well. For now, default.cgi was the order of the day.


#!C:/jrubywork/jruby/bin/jruby.bat

require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT)

# If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like:
# "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired
require "dispatcher"

ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) ifFile.directory?(dir) } if defined?(Apache::RubyRun)
Dispatcher.dispatch



Yes, running a Java interpreter on each CGI hit is gross. No, I don't expect anyone to run like this in production. It was simply the easiest way to get this working. Now we can go forward.

Step 3: Configure Apache

I'll admit, working in application servers from dawn to dusk I don't have to tweak Apache configs much. This config probably isn't the most spectacular thing in the world, but it does what it needs to. The biggest change from a standard Rails config is the env vars provided for JRuby.


DocumentRoot "C:/rails-1.0.0/public"

<Directory "C:/rails-1.0.0/public">
SetEnv JRUBY_HOME C:\\jrubywork\\jruby
SetEnv JAVA_HOME C:\\j2sdk1.5.0_06
Options Indexes ExecCGI FollowSymLinks
AddHandler cgi-script .cgi
AllowOverride all
Allow from all
</Directory>



Step 4: Turn off what doesn't work

Ok, you knew there had to be a catch. Since there's still a lot of work to be done on Rails, there was at least one area I discovered that wasn't going to work correctly today. To save myself staying up all night, I disabled session support; with it enabled, the script got stuck in a neverending loop somewhere I didn't feel like investigating. For the sake of this test, I added the following line to rails_info_controller.rb:


module Controllers #:nodoc:
class RailsInfoController < ApplicationController
session :disabled => true



We'll circle around to session management and get it working, so don't fret.

Step 5: Finally, test out our very narrow, very basic request

As you've probably gathered, the request I got Rails to handle was a simple info dump. Calling /rails_info/properties on a standard Rails install just dumps some version numbers and path information. It does, however, exercise the full Rails request handling and dispatching mechanism. Having it working is a big deal...it means that Rails is actually able to handle requests and display a result.

(as an aside, during the final stages of debugging I also saw the default error page come up, fully rendered and containing stack traces and error info...so even that's pretty cool).

On my system, /rails_info/properties outputs:

Ruby version1.8.2 (java)
Rails version1.0.0
Active Record version1.13.2
Action Pack version1.11.2
Action Web Service version1.0.0
Action Mailer version1.1.5
Active Support version1.2.5
Application rootC:/rails-1.0.0/public/../config/..
Environmentdevelopment
Database adaptermysql

The (java) up there represents the first time you or anyone else has seen Rails running in a JVM. Mark this day on your calendar.

Where to now?

It is may or may not be safe to say that Rails "runs" on JRuby. There's obviously a number of other subsystems to get working, and without sessions a web app would be pretty dumb. Saying that Rails works, except X and except Y, basically means it doesn't work without a whole bunch of asterisks--but handling an end-to-end request of any kind represents a major milestone.

What would be safe is to say that this represents the birth of Rails on JRuby. This is the first time a request has successfully been handled by Rails in a JVM. The next steps are obviously to get all normal use cases working, get the rest of the Action Pack functioning, and as always, speed JRuby up to be a viable deployment option.

To all those out there who have supported us and believed in us, we on the JRuby team give our thanks. This milestone would not have been possible without you.

To all those out there interested in Ruby, Rails, JRuby, or any combination of the three, I say this: You ain't seen nothing yet.