Daemonizing Rails

No, this isn’t a post about us converting from Linux to BSD. Instead, I’m talk about starting long running processes that use the Rails environment. If you’ve tried to do this, you’ve probably seen some obscure errors like:

closed stream activesupport-2.1.0/lib/active_support/buffered_logger.rb:105:in `write' activesupport-2.1.0/lib/active_support/buffered_logger.rb:105:in `flush' activesupport-2.1.0/lib/active_support/buffered_logger.rb:118:in `auto_flush' activesupport-2.1.0/lib/active_support/buffered_logger.rb:70:in `add' activesupport-2.1.0/lib/active_support/buffered_logger.rb:76: ...

Let’s look at why that happens.

Daemons in UNIX are a wonderful, magical thing. Even though it seems like starting up a background job should be simple, there are a lot of steps involved. You can see them in detail in Tammer Saleh’s Angels and Daemons talk. The really interesting issue for us is that processes close all open IO streams when they daemonize. That may not cause a problem for a normal Ruby Application, but it violates some assumptions that Rails makes.

When Rails boots, it opens a few different file descriptors. First, it opens a log file. This is your production.log or similar. It also makes a connection to the database. Rails gets unhappy when these files are closed out from under it. You could write a lot of code to handle these conditions or you can take the simple way out: Delay booting Rails until after you daemonize. Here’s a simple script that does that:

#!/usr/bin/env ruby
require 'rubygems'
require 'daemons'
ROOT = File.expand_path(File.dirname(__FILE__)+'/../')
Daemons.run_proc('watcher', :dir_mode => :normal, :dir=>"#{ROOT}/log/",:log_output =>true, :backtrace=>true) do
  require "#{ROOT}/config/environment"
  # Do work here
end

This code is simple, but there are a couple of subtleties. The first is that we need to use File.expand_path to turn the Rails Root path into an absolute path. When an application daemonizes, it changes its working directory to /. If we use a relative path for our RAILS_ROOT it will no longer point to our Rails installation.

Once we have the absolute RAILS_ROOT path, we can daemonize and then boot Rails. Rails will now start as usual and all files will be opened. That’s all there is to it. Now you can easily start long running processes using Rails. You can even use a cap task to easily restart them on deploy:

task :restart_watcher do
  run "#{current_path}/script/watcher restart"
end