Easy Rails daemons with Upstart

February 9, 2012

Back in 2010 when I wrote TweetedLinks I needed a couple of daemons running in the background. I’ve used tools like God and DaemonKit before, but honestly they are more hassle than they are worth. I wanted something really simple that would play nicely with Bundler and not complicate development and debugging. At the time, at work I was working onan old Rails app, so I looked to see how that did it.

The daemons for this were just a simple Ruby script which included the Rails environment, and then a loop with a delay. Every 10 seconds they would check whether there was anything to be processed, and if so process it. This daemons were controlled by a simple init script. There wasn’t any automatic restarting if they died, but we used Icinga/Nagios to ensure the processes are running. So I took that as a base, and got something up and running. Here is an example of one of the daemons I have, I am using Stalker for queuing which handles the loop:

#!/usr/bin/env ruby
require File.expand_path("../../config/environment", __FILE__)
STDOUT.sync = true

Stalker::job 'user.fetch_details' do |args|
  begin
    user = User.find(args['id'])
    user.fetch_user_details!
  rescue ActiveRecord::RecordNotFound # too fast
    Rails.logger.warn "Unable to find user ##{args['id']} - suspect too fast, requeuing"
    Stalker.enqueue('user.fetch_details', :id => args['id'])
  end
end
jobs = ARGV.shift.split(',') rescue nil
Stalker.work jobs

I have this stored in the scripts folder and have it chmodded so it is executable. The import bit is the first three lines.

Next I wanted to make the script automatically start and automatically restart if it goes down. The first bit could be solved with init scripts, however restarting is the important bit. For the last few years Ubuntu has included upstart, which handles all this for you. The configuration for this is stored in files in /etc/init, it is pretty simple so take a look. Here is what I’m using for TweetedLinks in /etc/init/tweetedlinks-ruby-worker.conf:

description "TweetedLinks Ruby Worker"

# automatically start
start on filesystem

# working directory
chdir /var/www/TweetedLinks/current

# command to run, with Bundler support!
env RAILS_ENV=production
exec bundle exec ruby script/worker.rb >> log/worker.log

respawn

You can then start this with the command start tweetedlinks-ruby-worker, stop it with stop tweetedlinks-ruby-worker and check it with status tweetedlinks-ruby-worker. Simple right? This logs to the file log/worker.log in the Rails directory for the app, and I wrote a post earlier on how to setup log rotation.