Rescuing From Rack::Timeout to Close MongoDB Connection

I’m using Rack::Timeout on Heroku to kill requests before Heroku’s 30 second limit is reached. This helps applications play nice with cloud infrastructure but can cause some unexpected bugs with MongoDB connections being reused by the next request.

The errors showed up as…

1
Mongo::ConnectionFailure: Expected response 372 but got 371

Mongo ruby driver >=1.3 catches the reused request and raises an error, but this still means the first and next requests both returned errors. It’s much better to catch the initial timeout and close the connection.

It seems like rescue_from Timeout::Error in ApplicationController should work, but for some reason the exception passed in is a Class and not Timeout::Error – most likely due to Rack::Timeout wrapping the entire app.

I googled around and couldn’t find a more elegant solution, but the below snippet in ApplicationController does the trick.

1
2
3
4
5
6
7
8
9
# note: can not rescue from Timeout::Error directly because a timeout from Rack::Timeout ends up passing in Class as the exception
rescue_from Exception do |exception|
  # catch Timeout::Error or message from Rack::Timeout
  if exception.is_a?(Timeout::Error) || /execution expired/ =~ exception.message
    # prevent subsequent requests from reusing this mongo connection
    Mongoid.database.connection.close
  end
  raise
end

As a side note, Heroku’s new cedar stack does not have the 30 second limit if you’re streaming data – Rails 3.1 supports streaming.