Author Archive for

03
Mar
09

/!\ FAILSAFE /!\ handling with rails versions prior to 2.3

Executive Summary

If you are running an application using a version of Ruby on Rails prior to 2.3 (including 2.2.2), and serving it with mongrel, you are at risk of serving up incorrect internal server pages.  Patch one file in rails in an uncontrovercial way and be happy again.  Skip to the end to see the patch.

The long and boring details

I had a problem with some infrastructure that was causing exceptions to (occasionally) be raised accessing the session store.  When this happened, it would result in rails dumping a message in the production.log with a distinctive keyword: “/!\ FAILSAFE /!\“.

Rails would then try to return a 500 status to the client, but in fact would return a 200 status with malformatted content:

Status: 500 Internal Server Error
Content-Type: text/html
 
<html><body><h1>500 Internal Server Error</h1></body></html>

Those two lines at the top that look like headers aren’t … they’re the beginning of the body of the response.  See the actual headers with ‘curl -o /dev/null –verbose’:

> GET / HTTP/1.1
> User-Agent: curl/7.16.3 (powerpc-apple-darwin9.0) libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3
> Host: localhost:3000
> Accept: */*

< HTTP/1.1 200 OK
< Connection: close
< Date: Tue, 03 Mar 2009 19:03:49 GMT
< Content-Type: text/html
< Content-Length: 122

Infrastructure upstream treated that response as cacheable, so the intermittent error got amplified by the lack of cache error.  Lots of clients wound up successfully fetching a document that said “500 Internal Server Error”.

We don’t like that.  We fixed the problem that caused the original “/!\ FAILSAFE /!\”, but a little digging revealed that any failsafe produced by rails served by mongrel for any reason would have this problem.

More digging revealed that the problem has to do with the way mongrel and rails interact.  In fact it has to do with the fact that mongrel subverts the CGI interface it flanges to rails with, and does so in a way that’s incompatible with the one rails code path that produces that FAILSAFE.  Rails tries to do the right thing, but fails because of mongrel’s cleverness.

All of this has been tested with Rails 2.0, 2.1, and 2.2.3 with mongrel 1.1.4. Rails 2.3 doesn’t have this problem.

Mongrel is the application server that accepts http connections and calls down into rails to generate responses.  It does this by constructing a Mongrel::CGIWrapper object (acts like and is-a Ruby standard libary CGI object) and an IOString (has a ‘write’ method) and passing it down to rails.

            cgi = Mongrel::CGIWrapper.new(request, response)

              Dispatcher.dispatch(cgi, ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS, response.body)

Mongrel has specific expectations about how that CGI will be used, and the usual code path through rails winds up in CgiResponse#out, where the output.write method gets called twice, once to write out the headers, and again to write out the body.

  class CgiResponse < AbstractResponse #:nodoc:

        output.write(@cgi.header(@headers))

          output.write(@body)

This might lead one to assume, as the author of the FAILSAFE handler did, that one could just write out headers and body as a whole document:

            fallback_output.write “Status: #{status}\r\nContent-Type: text/html\r\n\r\n#{body}”

Too bad, because mongrel is really depending on rails calling the *header* method on the CGI it passes down and using the return from that as the header portion that gets written to the output stream.  

Mongrel::CGIWrapper remembers the headers passed into CGI#header, and returns an empty string!  Then when mongrel goes to do the actual transmission it uses the stored headers.

Now, the FAILSAFE handler didn’t bother using the header method to format the two trivial headers, it just wrote the whole message to the output stream.  Mongrel assumed that since it didn’t know what headers it should send (or even what the status was) that everything was OK, and it should send its default 200 status headers and the body.  

FAIL

Once you see the problem (or if you took the advice at the top of this post simply skipped to the end) the solution is quite straightforward.  This approach treats the CGI and IOString passed down from mongrel in the way they’re expected, and mongrel does the right thing with it.  Since it causes the failsafe_response to behave the same as the rest of rails, it should work in any application server.  For all I know others have the same problem.

--- a/vendor/rails/actionpack/lib/action_controller/dispatcher.rb
+++ b/vendor/rails/actionpack/lib/action_controller/dispatcher.rb
@@ -54,13 +54,19 @@ module ActionController
       end

       # If the block raises, send status code as a last-ditch response.
-      def failsafe_response(fallback_output, status, originating_exception = nil)
+      def failsafe_response(fallback_output, status, originating_exception = nil, response = nil)
         yield
       rescue Exception => exception
         begin
           log_failsafe_exception(status, originating_exception || exception)
           body = failsafe_response_body(status)
-          fallback_output.write "Status: #{status}\r\nContent-Type: text/html\r\n\r\n#{body}"
+          if response
+            response.headers['Status'] = status
+            response.body = body
+            response.out(fallback_output)
+          else
+            fallback_output.write "Status: #{status}\r\nContent-Type: text/html\r\n\r\n#{body}"
+          end
           nil
         rescue Exception => failsafe_error # Logger or IO errors
           $stderr.puts "Error during failsafe response: #{failsafe_error}"
@@ -184,7 +190,7 @@ module ActionController
       end
 
       def failsafe_rescue(exception)
-        self.class.failsafe_response(@output, '500 Internal Server Error', exception) do
+        self.class.failsafe_response(@output, '500 Internal Server Error', exception, @response) do
           if @controller ||= defined?(::ApplicationController) ? ::ApplicationController : Base
             @controller.process_with_exception(@request, @response, exception).out(@output)
           else
 

07
Dec
07

Fixing Cmd-K in Mail.app

All right thinking people know that Cmd-K should always mean “make a hyperlink out of the selected text, and open a sheet for the link target, defaulting to the clipboard contents”.

Unfortunately, some deranged person inside the Mail team reassigned it to “steal my focus and bring up a scary looking alert, forcing me to startle, Cmd-. to cancel, then poke around menus to figure out how to insert a link, totally breaking my stride”.

Well, it turns out this bug is easy to work around:

  1. Open your System Preferences -> Keyboard & Mouse -> Keyboard Shortcuts
  2. Scroll to the bottom of the list and select “Application Keyboard Shortcuts”
  3. Click the little “+” button
  4. Pick Application: Mail, Menu Title: “Add…”, click in Keyboard Shortcut and do Cmd-KAdding a keyboard shortcut sheet
  5. Press “Add” and your control panel should look like:Keyboard Shortcuts fixed
  6. Restart Mail.app and it now behaves correctly.

This works for me in Leopard … should also work in Tiger and probably earlier.

What a relief!

15
Nov
07

Faking S3 for attachment_fu with ParkPlace

I’ve been fooling around with doing file storage on Amazon S3, and using Park Place to fake a real connection (for testing, working on the bus, saving fifteen cents, etc.)

Here are the notes I took away, using it on Leopard.

Host configuration:

$ sudo gem install -y aws-s3$ sudo gem install -y camping --source http://code.whytheluckystiff.net
$ sudo gem install -y mongrel activerecord
  2. mongrel 1.1.1 (ruby)
$ sudo gem install sqlite3-ruby --source http://code.whytheluckystiff.net
  2. sqlite3-ruby 1.2.1 (ruby)

Get parkplace:

cd /tmp && svn export http://code.whytheluckystiff.net/svn/parkplace/trunk parkplace
sudo ruby setup.rb
sudo ln -s /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/parkplace /usr/bin

Run parkplace: (this is screaming for some launchd love…)
parkplace &

Create a config/amazon_s3.yml file resembling:

development:
  bucket_name: myapp_development
  access_key_id: '44CF9590006BF252F707'
  secret_access_key: 'OtxrzxIsfpFjA7SwPzILwy8Bw21TLhquhboDYROV'
  server: localhost
  port: 3002
test:
  bucket_name: myapp_test
  access_key_id: '44CF9590006BF252F707'
  secret_access_key: 'OtxrzxIsfpFjA7SwPzILwy8Bw21TLhquhboDYROV'
  server: localhost
  port: 3002
production:
  bucket_name: your_bucket_name
  access_key_id: your_access_key_id
  secret_access_key: your_secret_access_key

(I’m OK with publishing those keys because I found them in parkplace bin/parkplace.)

Make sure that parkplace and config/amazon_s3.rb agree on what port to use.

If you change your model has_attachment to reflect :storage => :s3 suddenly all access to the model accesses storage on S3 (or your localhost fake).

Fun stuff:

  • parkplace generates a ~/.parkplace/park.db that can be accessed with sqlite3>
  • sqlite3 ~/.parkplace/park.db ‘select * from parkplace_bits;’
  • Files are under ~/.parkplace/storage/
  • parkplace has a nifty console application at http://whatever.com:8080/profile

… and again:
$ sudo gem install -y aws-s3
$ sudo gem install -y camping --source http://code.whytheluckystiff.net
$ sudo gem install -y mongrel activerecord
2. mongrel 1.1.1 (ruby)
$ sudo gem install sqlite3-ruby --source http://code.whytheluckystiff.net
2. sqlite3-ruby 1.2.1 (ruby)




June 2012
M T W T F S S
« Mar    
 123
45678910
11121314151617
18192021222324
252627282930  

Follow

Get every new post delivered to your Inbox.