Building a JRuby Quartz gem for scheduling; running into problems passing a JRuby object's class whe


#1

I had a lot of hope for the Quartz-Rails plugin that was created a while
ago:

http://www.jkraemer.net/2008/1/12/job-scheduling-with-jruby-and-rails

Unfortunately, it doesn’t work with the newest versions of JRuby and
looks
like it isn’t being maintained. I looked a bit at the Quartz
documentation
and think this can not only be updated, but done in such a way that all
the
configurations are done in YAML or Ruby rather than in XML and requiring
a
Servlet to work ­ this should be something that can be used for ANY kind
of
Ruby application.

I’m thinking that the interface from the Ruby side could look something
like
this:

JQuartz.schedule :jobname, :cron => "“0/20 * * * * ?” do

Some code here to be executed every 20 seconds

end

What is the best approach to this problem? Here are some of the
approaches I
see:

  1. Just use the JobDetail interface in Quarts:

     JobDetail job = new JobDetail("job1", "group1", 
    

SimpleJob.class);

In JRuby, this is:

jobDetail =

Java::OrgQuartz::JobDetail.new(java.lang.String.new(“myJob”),
java.lang.String.new(“myGroup”), RubyJob.new.java_class)

Unfortunately, the third parameter requires a Java class that conforms
to
the Job interface. Using “include” I can create a Ruby class that
implements
the interface, but I either get an error that it can’t accept
org.jruby.RubyClass or some crazy error like:

Problem instantiating class ‘org.jruby.gen.InterfaceImpl57204556’

Is there a way to pass a JRuby class to a Java object expecting a Java
class?

  1. Write all the scheduling madness in Java and invoke a JRuby
    interpreter.
    So far I’ve tried this:

public class RubyJob implements Job
{
public void execute(JobExecutionContext context) throws
JobExecutionException {
System.out.println(“Executing job”);
Ruby runtime = Ruby.newInstance();
runtime.evalScriptlet(“require ‘job.rb’”);
}
}

Probably not the best way to do things. Is this the preferred way to
execute
Ruby from Java?

My preference is to figure out a way to make option 1 work, since I can
write most of the gem in Ruby and not face the possibility of the JRuby
interface breaking. Any ideas on a lightweight solution?

Ikai


#2

Hi Akai,
i dont know if it is an option but You might try Crone4J.
It’s java library used by me pretty much, and since You use JRuby You
have a choice.

Best greetings,
Pawel Wielgus.

2009/1/14, Ikai L. removed_email_address@domain.invalid:

Ruby application.

  1. Write all the scheduling madness in Java and invoke a JRuby interpreter.
    }

Probably not the best way to do things. Is this the preferred way to execute
Ruby from Java?

My preference is to figure out a way to make option 1 work, since I can
write most of the gem in Ruby and not face the possibility of the JRuby
interface breaking. Any ideas on a lightweight solution?

Ikai


To unsubscribe from this list, please visit:

http://xircles.codehaus.org/manage_email

#3

On Thu, Jan 15, 2009 at 9:48 AM, Ikai L. removed_email_address@domain.invalid wrote:

Any ideas on a lightweight solution?
Hi,

you might also want to take a look at the gem “rufus-scheduler” :

http://github.com/jmettraux/rufus-scheduler
http://openwferu.rubyforge.org/scheduler.html

It works well for me on JRuby (1.1.2 to 1.1.5 at least).

Best regards,


John M. - http://jmettraux.wordpress.com


To unsubscribe from this list, please visit:

http://xircles.codehaus.org/manage_email

#4

Hi Ikai,

What do you mean by:

Unfortunately, it doesn’t work with the newest versions of JRuby
I’m currently running Jruby-1.5 with jruby-rack-0.9.2 on tomcat using a
patched version of the Quartz-Rails plugin from Kraemer. I’ve posted it
a few weeks ago on this list.

However using Quartz with a YAML-config file without the need of a
servlet would be very nice!

Andres


#5

Hi Ikai,

I was looking a few weeks ago for the same thing (JRuby + Quartz + Job
implementation in Ruby + not limited to a JEE environment). Because I’m
busy
at the moment, I can’t finish my analysis.

A few things I have learned:

  • I subclass JobDetail to make it possible to pass a job instance rather
    than a class name (RAILS_ROOT/lib/sharing_stateless_job_detail.rb):

include_class ‘org.quartz.JobDetail’
include_class ‘org.quartz.SchedulerException’

class SharingStatelessJobDetail < org.quartz.JobDetail

attr_accessor :job

def initialize(name, group, job)
super()
setName name
setGroup group
@job = job
end

def validate
raise org.quartz.SchedulerException.new(“Job’s name cannot be null”,
org.quartz.SchedulerException.ERR_CLIENT_ERROR) if getName == nil
raise org.quartz.SchedulerException.new(“Job’s group cannot be
null”,
org.quartz.SchedulerException.ERR_CLIENT_ERROR) if getGroup == nil
end
end

  • I create a new JobFactory which return my passed job instance, rather
    than
    create a new instance from the provided class
    (RAILS_ROOT/lib/sharing_stateless_job_factory.rb):

include_class ‘org.quartz.spi.JobFactory’
include_class ‘org.quartz.spi.TriggerFiredBundle’
include_class ‘org.quartz.JobDetail’

class SharingStatelessJobFactory
include org.quartz.spi.JobFactory

def newJob bundle
jobDetail = bundle.getJobDetail
jobDetail.job
end
end

  • My simple logging example (RAILS_ROOT/lib/logging_job.rb):

include_class ‘org.quartz.Job’
include_class ‘org.quartz.JobExecutionContext’

class LoggingJob
include org.quartz.Job

def execute context
RAILS_DEFAULT_LOGGER.info “LoggingJob scheduled at #{Time.now}”
end
end

  • And my initializer
    (RAILS_ROOT/config/initializers/quartz_scheduler.rb):

    require ‘java’
    require ‘lib/commons-logging-1.1.jar’
    require ‘lib/commons-collections-3.2.jar’
    require ‘lib/commons-pool-1.3.jar’
    require ‘lib/quartz-all-1.6.4.jar’

    require ‘sharing_stateless_job_factory’
    require ‘sharing_stateless_job_detail’

    include_class ‘org.quartz.impl.StdSchedulerFactory’
    include_class ‘org.quartz.JobDetail’
    include_class ‘org.quartz.CronTrigger’

    RAILS_DEFAULT_LOGGER.info ‘intializing Quartz Scheduler…’

    factory = org.quartz.impl.StdSchedulerFactory.new()
    scheduler = factory.scheduler
    scheduler.jobFactory= SharingStatelessJobFactory.new
    scheduler.startDelayed 5

    RAILS_DEFAULT_LOGGER.info ‘intializing LoggingJob…’
    job = SharingStatelessJobDetail.new(‘LoggingJob’, ‘schedulingGroup’,
    LoggingJob.new)
    trigger = org.quartz.CronTrigger.new(‘LoggingJobTrigger’,
    ‘schedulingGroup’, ‘0 * * * * ?’)
    scheduler.scheduleJob(job, trigger)

    RAILS_DEFAULT_LOGGER.info ‘Finished initializing quartz scheduler
    and
    jobs’

And a few things I have to think over:

  • If it executed in a JEE server (which starts more than one rails
    instances), how do you ensure, the job is only scheduled one time?
  • Quartz starts a new thread for the scheduling stuff. How we can stop
    the
    threads, if the user press CTRL + C if it starts rails with
    script/server?

I hope this helps a little bit…

Ragards (and sorry for my poor english),
Christian


#6

Christian, wow, thanks for the awesome detailed email! I’ll have to dig
back
into this when I get some free cycles.

Regarding a new thread ­ when you do jruby script/server, it starts a
new
instance of Java, so hitting Control-C should kill the instance of Java,
which should, in turn, cull all the running threads. Quartz doesn’t do
anything analogous to forking, so I don’t think this’ll be a problem.

Also ­ in a JEE server, I believe the SchedulerFactory will return the
same
scheduler. This is because even though multiple Rails instances are
started,
Rails instances can share static Java objects. This should not be an
issue
if you are on Rails 2.2, the threadsafe release of Rails, since only one
instance of Rails is started.

Thanks for the great email. I’ll look into this when I get a chance.
I’ll
also look at Crone4J and rufus-scheduler, per the other comments I’ve
received in this thread.

Ikai


#7

Hi Ikai,

today, I commit the first version of my quartz scheduler plugin. It has
some
issues, but you can play with this version and any notes are welcome…
You find the plugin with install instructions ans the list of known
issues
at http://github.com/muellerc/quartz_scheduler

Christian


#8

Hi Ikai!

My understanding (verified with a debugging session) is, Quartz create a
Threadpool with WorkerThreads (the default is 10). If I run a simple job
in
my java ide (eclipse) and exit the main method, the job is still
running.

If I run the job in rails and hitting Ctrl+C, I have the following
output:

chris:~/workspaceJRuby/scheduler mullerc$ jruby script/server
=> Booting Mongrel (use ‘script/server webrick’ to force WEBrick)
=> Rails 2.2.2 application starting on http://0.0.0.0:3000
=> Call with -d to detach
=> Ctrl-C to shutdown server
** Starting Mongrel listening at 0.0.0.0:3000
** Starting Rails with development environment…
Jan 17, 2009 1:20:59 PM org.quartz.simpl.SimpleThreadPool initialize
INFO: Job execution threads will use class loader of thread: main
Jan 17, 2009 1:20:59 PM org.quartz.core.SchedulerSignalerImpl
INFO: Initialized Scheduler Signaller of type: class
org.quartz.core.SchedulerSignalerImpl
Jan 17, 2009 1:20:59 PM org.quartz.core.QuartzScheduler
INFO: Quartz Scheduler v.1.6.4 created.
Jan 17, 2009 1:20:59 PM org.quartz.simpl.RAMJobStore initialize
INFO: RAMJobStore initialized.
Jan 17, 2009 1:20:59 PM org.quartz.impl.StdSchedulerFactory instantiate
INFO: Quartz scheduler ‘DefaultQuartzScheduler’ initialized from default
resource file in Quartz package: ‘quartz.properties’
Jan 17, 2009 1:20:59 PM org.quartz.impl.StdSchedulerFactory instantiate
INFO: Quartz scheduler version: 1.6.4
Jan 17, 2009 1:21:00 PM org.quartz.core.QuartzScheduler setJobFactory
INFO: JobFactory set to: org.jruby.gen.InterfaceImpl267287782@8434b1
intializing quartz scheduler…
intializing job ‘LoggingJob’…
quartz scheduler and all jobs initialized
** Rails loaded.
** Loading any Rails specific GemPlugins
** Signals ready. TERM => stop. USR2 => restart. INT => stop (no
restart).
** Rails signals registered. HUP => reload (without restart). It might
not
work well.
** Mongrel 1.1.5 available at 0.0.0.0:3000
** Use CTRL-C to stop.
Jan 17, 2009 1:21:05 PM org.quartz.core.QuartzScheduler start
INFO: Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED started.
LoggingJob scheduled at Sat Jan 17 13:21:05 +0100 2009
^C** INT signal received.
Exiting

I don’t know, why rails isn’t shutting down as expected. I must kill the
process… :o( Some test with the ShutdownHookPlugin from quartz are
also
not successful (with rails). Now, I will find out the reason…

Tomorrow, I will package the project with warbler and test the
SchedulerFactory
and Scheduler behavoir in a JEE environment.

I will commit the code of my tests project to
http://github.com/muellerc/quartz_scheduler/tree/master in a short
time…

Christian