Use Phusion Passenger for Development

Ruby on Rails is single-threaded, which means that an unusually long request such as a file upload or unruly query will block subsequent requests. This is why one of the most popular production architectures for Rails apps involves a cluster of Mongrel servers behind a reverse proxy such as Nginx. Each Mongrel handles a single request. But in your development environment, you may not want to deal with the additional configuration or resource overhead of setting up something similar.

Usually the above is not even a concern until you really do need your local application to handle more than one request at a time. Let me illustrate with an example. We’re working on an app in which an HTTP request “simulator” (within the same app) is used for testing inbound API requests. The code looks like this:

payload = 'stuff'
http = Net::HTTP.new('localhost',3000)
response = http.post2('/receiver', payload, {'Content-Type' => 'application/xml'})

and should post XML to http://localhost:3000/receiver. However, if you try running it by calling up a page in your browser, your request hangs and eventually times out. This is because the Rails dispatcher (through Mongrel) cannot process a second HTTP request - the XML post - because the first one - the browser request - is still running.

On aside, if you wrap the above into a new thread, you’ll see it does process immediately, e.g.

Thread.new {
  payload = 'stuff'
  http = Net::HTTP.new('localhost',3000)
  response = http.post2('/receiver', payload, {'Content-Type' => 'application/xml'})
}

but the last thing you want to do is introduce Ruby threading code into your non-thread safe Rails app (at least prior to version 2.2, which is still in ‘edge’ Rails as of this publish date).

Passenger to the Rescue

Before we go on, let me point out the lovely Passenger architectural overview, which explains how Apache prefork MPM is leveraged to spawn multiple instances of your Rails app. Each instance runs as a separate process but an internal application pool is used to conserve handles. The net result is that now your local Rails app will fork a new process for the next incoming request (while the first one is still running), and any code similar to the first example executes promptly. I want to emphasize that this is similar to having a pack of Mongrels on standby, each one ready to handle a new request. However, the overhead will be substantially less, especially if you use Ruby Enterprise Edition, also from the creators of Passenger.

Passenger on Mac OS X Leopard

Running Passenger on the Mac has been covered in detail in “Installing Phusion’s Passenger on Leopard (OS X 10.5),” a recommended step-by-step walk-through. You won’t need to compile Apache if you’re running 10.5 and a recent version of the Passenger gem. Overall, the configuration is quite easy, especially if you’ve dealt with Apache before.

Head’s up MacPorts users with a custom Apache install: If you’re having problems while running the passenger-install-apache2-module executable, you may need to specify the location of apxs in your path: export APXS2=/opt/local/apache2/bin/apxs

The only gotcha’s are remembering to explicitly set the environment to ‘development’ in the virtual host settings in your Apache config file:

  DocumentRoot /httpd/spyglass/public
  ServerName spyglass.local
  RailsEnv development

…and adding an entry for your app in /etc/hosts, which we recommend if you work on multiple apps locally:

127.0.0.1 spyglass.local

Easy as Pie*

If you don’t want to deal with raw Apache settings or editing your hosts file, be sure to check out the Passenger preferences panel found here. It allows you to drag and drop your existing Rails app into a preferences window and will automatically configure all of the settings for you. The settings are persisted to a file located in /etc/apache2/passenger_pane_vhosts/ in case you want to tweak them manually.

*I like pie

Logging Caveat

While log entries will continue to appear under log/development.log, any Ruby-specific logging is dumped into Apache’s error log, /var/log/apache2/error.log (also accessible through the Mac Console). So for example, a “puts @some_variable” will be piped into Apache’s log. Therefore, if you are relying on any debug statements in your code, simply use logger.debug(’string’) instead, and it will appear in development.log.


About this entry