Switch_off Switch_on Switch_off
Inventive Labs: Web Problem Solvers

Scheduling periodic tasks in a Rails application [08112007]

There's a number of solutions around for forking out of the Rails http request cycle and performing intermittent or long-running operations within your Rails application environment. Some of the most well-documented options are summarised on the Rails wiki.

Looking for a way to schedule actions like 'fetch this feed every 15 minutes', 'clear out old sessions from the db', and 'take a snapshot of each site' for Blueprint, I investigated most of these options.

In the past I've used the reliable cron/runner combination, but this requires additional setup -- outside of your app -- on each server where the app is deployed. There are recipes out there for updating the cron table via Capistrano, but this is difficult to do in a way that preserves existing cron configuration. BackgrounDrb and AP4R both look accomplished, but are really intended to solve other (and bigger) problems.

OpenWFEru is a Workflow Engine, which sounds impressively enterprisey. I don't know much about that, but it comes with a pretty good scheduler — check out the API for it here.

This can be used to create a cron-like scheduler within Rails. I wanted the scheduler to run as a separate process alongside the pack of Mongrels that handle web requests, started and stopped by Capistrano. Ruby has a great 'daemons' gem that takes care of daemonising processes for you.

If you need something like this, install these two gems:

$ sudo gem install -y openwferu daemons

Then create script/schedule:

#!/usr/bin/env ruby
require 'rubygems'
require 'daemons'
APP_DIR = File.join(File.dirname(File.expand_path(__FILE__)), '..')

Daemons.run_proc(
  'schedule',
  :dir_mode => :normal, 
  :dir => File.join(APP_DIR, 'log'),
  :multiple => false,
  :backtrace => true,
  :monitor => true,
  :log_output => true
) do

  # Daemonising changes the pwd to /, so we need to switch 
  # back to RAILS_ROOT.
  Dir.chdir(APP_DIR)

  # Load our Rails environment.
  require File.join('config', 'environment')

  begin
    # Initialise the OpenWFE scheduler object.
    require 'openwfe/util/scheduler'
    scheduler = OpenWFE::Scheduler.new
    scheduler.start

    # Now assign jobs to the scheduler (see API). For example:
    scheduler.schedule_every('15m') { Feed.fetch_and_consume! }

    # Tell the scheduler to perform these jobs until the 
    # process is stopped.
    scheduler.join
  rescue => e
    RAILS_DEFAULT_LOGGER.warn "Exception in schedule: #{e.inspect}"
    exit
  end
end

Chmod this file to be executable, then test it:

$ chmod a+x script/schedule
$ script/schedule start

Monitor the file at log/schedule.output -- most errors will be reported here. It will log to log/<environment>.log. Run 'script/schedule' with no arguments to see how to stop and restart it, and do other daemonoid things if you like.

You can then create a task within your Capistrano deployment recipies along the lines of:

task :after_restart do
  run "RAILS_ENV=#{rails_env} #{current_path}/script/schedule stop"
  run "RAILS_ENV=#{rails_env} #{current_path}/script/schedule start"
end

Yet another web developer [Fri 16 Nov 2007, 8:23am] said:

Just found your site, and I must say your light switch is damn geeky cool ;) Good luck

Walt [Sun 15 Jun 2008, 11:35am] said:

Hi Joseph,

I am having problems using controllers in my backgrounDRB workers - to producing invoices for instance.

Will schedule "know" the entire Rails environment and gems (ApplicationController) - and thus allow me to instantiate an object of ApplicationController in one of my models (and the model being called in the schedule context)?

like:

class Invoice

  viewer = Class.new(ApplicationController)

..... end

cheers, walt

Joseph [Mon 16 Jun 2008, 1:06am] said:

Well, yes, the scheduler will load the full Rails environment (see the 'require' line in the script) -- but instantiating controllers is not exactly a great idea, if you can avoid it. This is because so much of their initialisation assumes the existence of a real CGI request, which obviously you don't get from a script like this.

A better idea would be to refactor your controllers so that the functionality you're trying to access is in a model instead.

exarcetox [Sun 24 Aug 2008, 8:05am] said:

Есть Казань мужчины, которые в женщинах не нуждаются. Нет-нет г казань , у них все в порядке с сайт казани потенцией. Непорядок у них исключительно в голове. Подозреваю, что казань ооо женщина для них - нечто среднее между компании казани инопланетянином, с которым казань интернет трудно найти общий язык, и чесоткой, которая время от казань 2008 времени отвлекает от важных дел, и избавиться от школы казани нее нет никакой возможности. Я таких мужчин из телефоны казани вежливости, мягко, называю самодостаточными. вузы казани А женщины им не нужны потому, что у них и так все есть: работа, разнообразные адрес казань увлечения, места, в которых они проводят досуг. база казань Женщина для них - досадная помеха, которая может г казань ооо заставить перекроить устоявшийся агенства казань порядок вещей. За который они цепляются так зао казань судорожно, как турист, по ошибке забредший на нудистский справочник казани пляж, - за свои плавки.

Only the comment field is required. Omitting the ID fields increases your risk of being mistaken for spam.

Preview or