Jens Krämer

Job scheduling with JRuby and Rails

 |  jruby, quartz, rails

As promised earlier, here’s the first of several articles I’m planning to write about running Rails on JRuby. Originially I wanted to start this little series with some kind of ‘getting started with JRuby on Rails’ guide. Since I didn’t find the time (or, say, motivation) to write one for weeks now, I decided to skip right through to some more advanced topics. So for this post, I’m assuming you already got your hello world JRuby on Rails project up and running and deployed to the application server of your choice. If this is not the case, have a look in the Wiki for documentation about getting started. It’s also worth following the various relevant blogs, as well as the jruby and jruby-extras mailing lists.

Ok, this post is titled Job scheduling with JRuby and Rails for a reason, so let’s get started with this now.

While Rails itself nowadays runs quite flawless inside application servers like JBoss or Glassfish, the usual way to handle background job (push it to some external daemon) doesn’t fit a J2EE application server environment particularly well. Besides the fact that I couldn’t think of an easy way to get BackgrounDrb running from my application’s WAR file, it simply doesn’t feel right to have any extra daemons like BackgrounDRb running besides that fat application server.

As it turns out, there are several ways to do better.

The GoldSpike solution

The kind folks from the jruby-extras team provide two servlets, RailsTaskServlet, and RailsPeriodicalTaskServlet as part of the GoldSpike plugin. These servlets can be used to run arbitrary Ruby code inside the context of your Rails application either once or periodically every n seconds. To schedule a job running YourJobClass.do_stuff every minute, you would place the following declaration into your web.xml:

<servlet>
  <servlet-name>periodicalTask</servlet-name>
  <servlet-class>org.jruby.webapp.RailsPeriodicalTaskServlet</servlet-class>
  <load-on-startup>1</load-on-startup>
  <init-param>
    <param-name>interval</param-name>
    <param-value>60</param-value>
    <param-name>script</param-name>
    <param-value>YourJobClass.do_stuff</param-value>
  </init-param>
</servlet>

Oh the joy of XML configuration files ;-)

While this works great, I had to run a job not every some seconds, but once a week on a defined day and time. Besides that, declaring a separate servlet for each single background job seems like overkill. Back from my Java days I knew that the Quartz library provided exactly what I needed - support for cron patterns. So the challenge was to get Quartz run my Ruby script inside the context of my application.

The rails_quartz plugin

Our target platform was JBoss, which already includes the Quartz library. I’m not sure about other app servers, if yours doesn’t have this already, just download quartz and put the jar including any dependencies somewhere in your application so it ends up inside the WEB-INF/lib folder of your WAR file. With Warbler, which is my preferred way to package JRuby on Rails applications, RAILS_ROOT/lib is good place, since it will pick up any jars from there automatically.

How does it work?

The plugin provides a ContextListener, which, when declared in web.xml, looks for any job declarations and tells the Quartz Scheduler about them. Here’s an example web.xml snippet scheduling a job to run every friday at 10 am:

<context-param>
  <param-name>yourJobCommand</param-name>
  <param-value>YourJobClass.do_stuff</param-value>
</context-param>
<context-param>
  <param-name>yourJobCronPattern</param-name>
  <param-value>0 0 10 ? * 6</param-value>
</context-param>

<listener>
  <listener-class>org.jruby.webapp.quartz.QuartzContextListener</listener-class>
</listener>

As you see, I use context parameters to configure the command to run, and the cron pattern to use. You can declare any number of jobs you want, just stick to the naming scheme for the parameters: <jobName>Command and <jobName>CronPattern so the listener can find out which pattern belongs to which job.

You can get the plugin on GitHub. As always, any feedback is welcome.