Timing out Rails from Mongrel

While we’re on the timeout tip, I’d like to talk about a fail-safe timeout for mongrel rails. We’ve talked about making sure that net/http and memcache-client behave, but what about other slow actions? Since mongrel only processes one rails request at a time, other requests can start to pile up. In rare cases, those other requests would wait forever. It would be nice to make sure that each rails request takes no longer than a set amount of time.

Since there is no timeout around a rails request in mongrel, we decided to put one in. Mongrel rails uses a mutex around all rails requests (taken from the process method in mongrel-1.0.1/lib/mongrel/rails.rb line 77):

@guard.synchronize {
  @active_request_path = request.params["PATH_INFO"]
  Dispatcher.dispatch(cgi, ActionController::CgiRequest::DEFAULT...
  @active_request_path = nil
}

Our solution was to redefine the @guard instance variable to make sure the rails request timed out after the lock was obtained. We first constructed a simple TimeoutMutex class:

class TimeoutMutex < Mutex
  def initialize(timeout = 30)
    super()
    @timeout = timeout
  end

  def synchronize
    lock
    begin
      Timeout::timeout(@timeout) {yield}
    ensure
      unlock
    end
  end
end

We then popped open the RailsHandler class in mongrel and redefined @guard:

class Mongrel::Rails::RailsHandler
  cattr_accessor :rails_timeout
  alias :original_initialize :initialize
  @@rails_timeout = 30

  def initialize(dir, mime_map = {})
    original_initialize(dir, mime_map = {})
    @guard = TimeoutMutex.new(@@rails_timeout)
  end
end

The @guard mutex is also used in the reload! method, so that will also get the same timeout. Given the fact that we don’t use this method and mongrel startup reads: “HUP => reload (without restart). It might not work well.”, we decided not to worry about it.

If you want to have a different timeout value, just set Mongrel::Rails::RailsHandler.rails_timeout. Since timeout throws an exception into the thread it times out, you could end up with an exception at any point in your rails code. Most of the time, this would tell you which methods need attention. You can also catch the Timeout::Error exception in you application controller’s rescue_action_in_public method.

This entry was posted in Extensions, Mongrel, Ruby. Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

2 Comments

  1. Posted December 7, 2007 at 10:33 am | Permalink

    Doesn’t mongrel have a default 60 second timeout for all actions?

    I see the TimeoutError raised in the reap_dead_workers function (mongrel.rb:665) in our logs occasionally when there’s a backup.

  2. Posted December 10, 2007 at 2:01 pm | Permalink

    No. You can test this by creating an action that sleeps forever and see what happens. I stopped watching after hanging the browser for 30 minutes.

    Mongrel calls reap_dead_workers for 3 different reasons: too many open files (lines 651 & 746), max processors (734) and graceful shutdown (688). Graceful shutdown gives workers 60 seconds to finish, then kills them. Too many open files should only happen if you have a large number of requests or some other process on the machine is eating up file handles. Max processors can be set when calling HttpServer.new, but defaults to 1 Billion (2**30 – 1).

    Keep in mind this is all on mongrel-1.0.1. I haven’t looked through the code for later versions.

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>