Scheduling tasks in Ruby / Rails

Whether it is retrieving an RSS feed, or performing some maintenance operations on your system or a Rails app, an easy to use scheduler is a must have. Motivated by this need, I did some scouring and came up with a few nifty ways to accomplish the task. Now, before we go on, it's worth mentioning that all *nix based systems have the advantage of a cron daemon which can perform this job quiet effectively. However, building standalone scripts is not always the best way to get things done, hence there are some valid cases where replicating this functionality can, in fact, be very useful.

Thread based scheduler

The most obvious way to schedule a task from within your program is to either loop it forever with a periodic sleep interval, or launch a separate thread which will perform the same job:

require 'fastthread'

t = Thread.new do
  while true do
    p 'perform task'
    sleep 5
  end
end

t.join # wait for thread to exit (never, in this case)

Create a new thread, perform the task, sleep for 5 seconds, and repeat forever. Launching one of these inside a Tier IV datacenter will ensure that your tasks are executed until the next nuclear winter. (Or, until your thread crashes - whichever is more likely!)

Flexible scheduling in Ruby

While threads are easy to use, they are still not very intuitive. After some searching, I stumbled across the OpenWFEru project, which implements a really nifty standalone-ready scheduler. Take a look yourself:

require 'openwfe/util/scheduler'
include OpenWFE

scheduler = Scheduler.new
scheduler.start

# Simple usage
scheduler.schedule_every('2s') { p 'every 2 seconds' } 
scheduler.schedule_every('5m') { p 'every 5 minutes' }
scheduler.schedule_every('1d') { p 'every day' }

# More fine-grained control + schedule task once.
scheduler.schedule_in('3d2m10s') do
    p 'after 3 days 2 minutes and 10 seconds stopping the scheduler and exiting...'
    scheduler.do_stop
end

# Cron-string scheduling. There is something for everyone here!
scheduler.schedule('1-60 * * * *') do
    p 'perform task'
end

scheduler.join

OpenWFEru scheduler is now also available as a standalone gem thanks to John Mettraux.

Each call to the scheduler queues your request on the main thread, which then spawns a separate thread for each task when the time is right. It's definitely quite a bit cleaner, but the rich parse engine is by far the best part. Even a non-programmer will understand this code. Plus, and if thats your preference, you have the ability of providing a cron-like string! Also, it is important to note that if you schedule a task for every 2 seconds, but the task itself takes 3 seconds, then the job will be performed every 5 seconds! If you really do need 2 second intervals, simply create a new thread in your scheduled block, and let it run.

Scheduling in Rails

Finally, if you want to schedule tasks in Rails, then BackgrounDRB is your new best friend. Ezra wrote a great guide for it, and I highly recommend that you check it out. As of 0.2.1, backgroundrb added support for easy job scheduling via a yaml config file (conf/backgroundrb_schedules.yml). Here is a sample:

scheduled_task:
  :class: :scheduled_task_worker
  :worker_method: :do_work
  :trigger_args:
    :start: <%= Time.now + 10.minutes %>
    :repeat_interval: <%= 1.hour %>

Note that you can have multiple workers, running at different intervals, simply duplicate the worker in your config and give it a different name. Then, call up the generator to create the file itself and navigate to lib/workers. Again, here is a sample implementation:

class ScheduledTaskWorker < BackgrounDRb::Worker::RailsBase
   
  def do_work(args)
    # RailsBase worker gives this class access to all ActiveRecord models.. go wild!
    p 'perform task'
  end

end
ScheduledTaskWorker.register

Once the files are in place, fire up the backgroundrb daemon, launch your Rails app, and you're good to go! If you haven't already, make sure you check out the RDoc for all the extra goodies.

Update: Several readers have also pointed me to RailsCron, but with one little gotcha.

Ilya GrigorikIlya Grigorik is a web ecosystem engineer, author of High Performance Browser Networking (O'Reilly), and Principal Engineer at Shopify — follow on Twitter.