rufus-scheduler is a Ruby gem for scheduling pieces of code (jobs). It understands running a job AT a certain time, IN a certain time, EVERY x time or simply via a CRON statement.
rufus-scheduler is no replacement for cron/at since it runs inside of Ruby.
A list of related Ruby projects :
github.com/javan/whenever github.com/yakischloba/em-timers/
More like complements :
github.com/mojombo/chronic/ github.com/hpoydar/chronic_duration
sudo gem install rufus-scheduler --source http://gemcutter.org
The usage is similar to the one of the old rufus-scheduler. There are a few differences though.
require 'rubygems' require 'rufus/scheduler' scheduler = Rufus::Scheduler.start_new scheduler.in '20m' do puts "order ristretto" end scheduler.at 'Thu Mar 26 07:31:43 +0900 2009' do puts 'order pizza' end scheduler.cron '0 22 * * 1-5' do # every day of the week at 22:00 (10pm) puts 'activate security system' end scheduler.every '5m' do puts 'check blood pressure' end
This code summons a plain version of the scheduler, this can be made more explicit via :
scheduler = Rufus::Scheduler::PlainScheduler.start_new
This PlainScheduler accepts a :thread_name option :
scheduler = Rufus::Scheduler::PlainScheduler.start_new(:thread_name => 'my scheduler')
might be helpful when tracking threads.
Note that is there is EventMachine present and running,
scheduler = Rufus::Scheduler.start_new
will return an instance of Rufus::Scheduler::EmScheduler (leveraging EventMachine).
Note that if you have a tiny script like this one :
require 'rubygems'; require 'rufus-scheduler' scheduler = Rufus::Scheduler.start_new scheduler.at 'Thu Mar 26 07:31:43 +0900 2009' do puts 'order pizza' end
And you run it, it will exit immediately.
If you place
scheduler.join
at then end of it will make the current (main) thread join the scheduler and prevent the Ruby runtime from exiting.
You shouldn’t be exposed to this issue when using EventMachine, since while running EM, your runtime won’t exit.
Scheduled blocks accept 0 or 1 parameter (this unique parameter is the job instance itself).
scheduler.every '5m' do puts 'check blood pressure' end scheduler.every '1y' do |job| puts "check cholesterol levels (#{job.job_id})" end
See the class Job for more details :
rufus.rubyforge.org/rufus-scheduler/classes/Rufus/Scheduler/Job.html
require 'rubygems' require 'rufus/scheduler' p Rufus.parse_time_string '500' # => 0.5 p Rufus.parse_time_string '1000' # => 1.0 p Rufus.parse_time_string '1h' # => 3600.0 p Rufus.parse_time_string '1h10s' # => 3610.0 p Rufus.parse_time_string '1w2d' # => 777600.0 p Rufus.to_time_string 60 # => "1m" p Rufus.to_time_string 3661 # => "1h1m1s" p Rufus.to_time_string 7 * 24 * 3600 # => "1w"
Jobs will, by default, trigger in their own thread. This is usually desirable since one expects the scheduler to continue scheduling even if a job is currently running.
Jobs scheduled with the :blocking parameter will run in the thread of the scheduler, blocking it.
scheduler.in '20m', :blocking => true do puts "order ristretto" sleep 2 * 60 end scheduler.in '21m' do puts "order espresso" end
Hence, our espresso wil come in 22 minutes instead of 21.
This job will execute every 3 days, but first time will be in 5 days from now :
scheduler.every '3d', :first_in => '5d' do # do something end
This job will execute every 3 days, starting from Christmas Eve at noon :
scheduler.every '3d', :first_at => '2009/12/24 12:00' do # do something end
The chronic gem may help (chronic.rubyforge.org/) :
require 'chronic' # sudo gem install chronic scheduler.every '3h', :first_at => Chronic.parse('this tuesday 5:00') do # do something starting this tueday end
‘at’ and ‘in’ jobs fire once only. ‘cron’ and ‘every’ jobs do fire repeatedly, so it might be useful to stop them.
scheduler.every '3d' do |job| l = determine_crop_maturity_level() if l >= 7 puts "crop is ready." job.unschedule else puts "crop not yet ready..." end end
In this example, the ‘every’ job will unschedule itself when the crop is ready.
Sometimes passing a block isn’t that convenient :
class JobThing def initialize (relevant_info) @ri = relevant_info end def call (job) do_something_about_it end end # ... scheduler.in '3d', JobThing.new('http://news.example.com/data_xyz') scheduler.in '1w', JobThing.new('http://news.example.com/data_abc'), :timeout => '1d'
rufus-scheduler accepts anything that responds to a call method with a unique parameter (it will pass the job) as a ‘schedulable’.
For compatibility with older (1.x) versions, schedulables with a trigger methods are accepted :
class JobThing def trigger (params) job = params[:job] end end
The ‘params’ correspond to the scheduler job params, and the key :job points to the rufus-scheduler job for the schedulable that is passed to a ‘call schedulable’.
scheduler.jobs # returns a map job_id => job of at/in/every jobs scheduler.cron_jobs # idem for cron jobs scheduler.all_jobs # idem but for every/at/in/cron jobs (all of them) scheduler.find_by_tag(t) # returns all the jobs with a given tag (passed at schedule time with :tags)
The ‘scheduling’ methods always return an instance of Rufus::Scheduler::Job. This object can be used for unscheduling :
job = scheduler.in '2d', :tags => 'admin' do run_backlog_cleaning() end # later ... job.unschedule # or scheduler.unschedule(job.job_id)
You can specify tags at schedule time :
scheduler.in '2d', :tags => 'admin' do run_backlog_cleaning() end scheduler.every '3m', :tags => 'production' do check_order_log() end
And later query the scheduler for those jobs :
admin_jobs = scheduler.find_by_tag('admin') production_jobs = scheduler.find_by_tag('production')
One can specify a timeout for the triggering of a job.
scheduler.every '2d', :timeout => '40m' do begin run_backlog_cleaning() rescue Rufus::Scheduler::TimeOutError => toe # timeout occurred end end
This job will run every two days. If a run takes more than 40 minutes it will timeout (its thread will receive a TimeOutError).
This timeout feature relies on an ‘in’ job scheduled at the moment the main job gets triggered, hence the ‘40m’ time string format.
By default, when exception occur when a job performs, the error message will be output to the STDOUT.
It’s easy to customize that behaviour :
scheduler = Rufus::Scheduler::PlainScheduler.start_new # or #scheduler = Rufus::Scheduler::EmScheduler.start_new def scheduler.handle_exception (job, exception) puts "job #{job.job_id} caught exception '#{exception}'" end
For backward compatibility, overriding #log_exception is still OK :
def scheduler.log_exception (exception) puts "caught exception '#{exception}'" end
Note that an every job or a cron job will stay scheduled even if it experiences an exception.
The default frequency for the scheduler is 0.330 seconds. This means that the usual scheduler implementation will wake up, trigger jobs that are to be triggered and then go back to sleep for 0.330 seconds. Note that this doesn’t mean that the scheduler will wake up very 0.330 seconds (checking and triggering do take time).
You can set a different frequency when starting / initializing the scheduler :
require 'rubygems' require 'rufus/scheduler' scheduler = Rufus::Scheduler.start_new(:frequency => 60.0) # for a lazy scheduler that only wakes up every 60 seconds
rufus-scheduler 2.0 can be used in conjunction with EventMachine (github.com/eventmachine/eventmachine/).
More and more ruby applications are using EventMachine. This flavour of the scheduler relies on EventMachine, thus it doesn’t require a separate thread like the PlainScheduler does.
require 'rubygems' require 'eventmachine' EM.run { scheduler = Rufus::Scheduler::EmScheduler.start_new scheduler.in '20m' do puts "order ristretto" end }
ruby 1.8.6, ruby 1.9.1p0 on jruby 1.2.0 it has some tiny issues (spec/blocking_spec.rb)
the ruby gem ‘eventmachine’ if you use Rufus::Scheduler::EmScheduler, else no other dependencies.
On the rufus-ruby list :
groups.google.com/group/rufus-ruby
rubyforge.org/tracker/?atid=18584&group_id=4812&func=browse
irc.freenode.net #ruote
github.com/jmettraux/rufus-scheduler
git clone git://github.com/jmettraux/rufus-scheduler.git
github.com/jmettraux/rufus-scheduler/blob/master/CREDITS.txt
John Mettraux, [email protected], jmettraux.wordpress.com
MIT