Ruby/Rack/Rails FastCGI

Using Rails with FastCGI is supposed to be easy. You just need a “start” script (usually saved as public/dispatch.fcgi, although this is not a real requirement).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
require_relative '../config/environment'

class Rack::PathInfoRewriter
  def initialize(app)
    @app = app
  end

  def call(env)
    env.delete('SCRIPT_NAME')
    parts = env['REQUEST_URI'].split('?')
    env['PATH_INFO'] = parts[0]
    env['QUERY_STRING'] = parts[1].to_s
    @app.call(env)
  end
end

Rack::Handler::FastCGI.run  Rack::PathInfoRewriter.new(APPNAME::Application)

You’ll have to add ‘fcgi’ to your Gemfile.

I use the following run script with runit:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#!/bin/sh

exec 2>&1

export RAILS_ENV=production
export LANG=en_US.UTF-8
cd /srv/www/users/www-user/MyApp

export HOME=~www-user

exec /usr/bin/spawn-fcgi -s /var/run/lighttpd2-MyApp-www-user.sock -n -u www-user -U www-data -l -- \
    /srv/www/users/www-user/.rvm/bin/ruby-1.9.3-p194 /srv/www/users/www-user/MyApp/public/dispatch.fcgi

EPIPE bug

If you’re using unix sockets for your application, you might see this (or similar) from time to time:

~/.rvm/gems/ruby-1.9.3-p194/gems/rack-1.4.1/lib/rack/handler/fastcgi.rb:93:in `flush': Broken pipe (Errno::EPIPE)

This happens if the client closes the connection to the web server, and the web server closes the connection to the FastCGI backend.

The problem is that ruby terminates (this is not an “exception”, so you cannot catch it); a good sysadmin will use runit (or similar), so it will be restarted, but as long as it is down, connections to the backend won’t get accepted, resulting in 500 Internal Server errors or similar.

To fix this bug, we need to change two things:

ruby-fcgi

My version is fcgi-0.8.8 from 2010… I changed the CHECK_STREAM_ERROR in fcgi-0.8.8/ext/fcgi/fcgi.c (whitespace fail – mixing tabs and spaces):

1
2
3
-           errno = err;\
-      rb_sys_fail(NULL);\
+      rb_raise(eFCGIStreamError, "unknown error (syscall error)");\

Rebuild and install it (shell in fcgi-0.8.8/ext/fcgi):

1
make && make install

Now we get a real exception we can catch:

rack-fastcgi

rack-1.4.1/lib/rack/handler/fastcgi.rb

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
--- fastcgi.rb.old      2012-09-11 23:15:40.436000011 +0000
+++ fastcgi.rb  2012-09-11 22:52:07.380000011 +0000
@@ -25,7 +25,11 @@
           STDIN.reopen(TCPServer.new(options[:Host], options[:Port]))
         end
         FCGI.each { |request|
-          serve request, app
+          begin
+            serve request, app
+          rescue StandardError => e
+            STDOUT.print "#{e}\n#{e.backtrace.join("\n")}\n"
+          end
         }
       end

The other parts of the handler already handle exceptions (closing the request and so on).

Now you still get the errors in the log, but ruby doesn’t crash anymore.

Generated using nanoc and bootstrap - Last content change: 2012-09-11 23:34