views:

513

answers:

4

Are Rails controllers multithreaded?

If so, can I protect a certain piece of code (which fires only once every ten minutes) from being run from multiple threads by simply doing

require 'thread'
Thread.exclusive do
     # stuff here
end

on do I need to somehow synchronize on a monitor?

A: 

My understanding is that a new controller instance is created for each HTTP request that is processed by a controller.

John Topley
Right, that would be exactly like a servlet.So then thread competition could be a problem with single-threaded resources.
Yar
Seems to be wrong, John. Could you verify it and correct your answer or delete it, please?
Yar
Why do you write that? Controller instance variables only exist for the lifetime of one request.
John Topley
Sorry John, I'm trying to make sense of this. See the other answers. Citing something might help.
Yar
I could well be wrong. I'm sure I read somewhere that a new controller instance is created per request, but typically I can't find the reference now! And Googling this hasn't given much information.
John Topley
Thanks John, it does in fact seem like you're right. It's also obvious now that I think about it. Otherwise you couldn't use @whatever variables as we are told to in Rails.
Yar
I just did a test in Rails 2.1. Controller instances are created per request.
Joel
A: 

what do you want to do?

That doesnt look like the best practice (for anything)...

Lichtamberg
I know it's not best practice. It's worst practice. But it's a long story in which threadsafety will resolve the current problem, and avoid solving the real problem (which is pretty deep and costly).
Yar
This isn't an answer. Please delete it or edit it to make it one. Thanks!
Yar
+2  A: 

Ruby is single threaded. So at anytime, a controller can only handle one request at a time. If there are more than one requests, these requests are queued up. To avoid this, people usually run a small set of Mongrels to get good concurrency. It works like this(straight from Mongrel WIKI FAQ):

  1. A request hits mongrel.
  2. Mongrel makes a thread and parses the HTTP request headers
  3. If the body is small, then it puts the body into a StringIO
  4. If the body is large then it streams the body to a temp file
  5. When the request is "cooked" it call the RailsHandler.
  6. The RailsHandler sees if the file is possibly page cached, if so then it sends the cached page.
  7. Now you're finally ready to process the Rails request. LOCK!
  8. Still locked, Mongrel calls the Rails Dispatcher to handle the request, passing in the headers, and StringIO or Tempfile for body.
  9. When Rails is done, UNLOCK! . Rails has (hopefully) put all of its output into a StringIO.
  10. Mongrel then takes this StringIO output, any output headers, and streams them back to the client super fast.

    Notice that if there is no locking if the page is cached.

ez
excellent. Do the Mongrel instances share, say, $global variables?
Yar
+2  A: 

Running rake middleware on a basic rails app gives the following:

use Rack::Lock
use ActionController::Failsafe
use ActionController::Reloader
use ActiveRecord::ConnectionAdapters::ConnectionManagement
use ActiveRecord::QueryCache
use ActiveRecord::SessionStore, #<Proc:0x017fb394@(eval):8>
use ActionController::RewindableInput
use ActionController::ParamsParser
use Rack::MethodOverride
use Rack::Head
run ActionController::Dispatcher.new

The first item on the rack stack is Rack::Lock. This puts a lock around each request, so only one request is handled at a time. As such a standard rails app is single threaded. You can however spawn new threads within a request that would make your app multi threaded, most people never encounter this.

If you are having issues…

require 'thread'
Thread.exclusive do
     # stuff here
end

… would ensure that stuff inside the block is never run in parallel with any other code. Creating a shared Mutext between all threads (in a class variable or something, but this could be wiped when reloaded in dev mode, so be careful), and locking on it as Rack::Lock#call does is to be preferred if you just want to ensure no two instances of the same code is executed at the same time.

Also, for the record, each request creates and dereferences one controller in each request cycle. No two requests should see the same instance, although they may see the same class.

Setting config.threadsafe! voids almost everything I said. That removes Rack::Lock from the stack, and means you will need to set a mutex manually to prevent double entry. Don't do it unless you have a really good reason.

Even without Rack::Lock you will still get one controller instance per request. The entry point to your controller ensures that, notice the call to new in process.

cwninja
Could you address cwa's answer and this statement in the Mongrel FAQ, "While Rails is running there is only one controller in operation at a time." What does that mean, in operation at one time... that multiple threads are passed through a single instance like Java servlets?
Yar
BTW, I ended up synchronizing on a Mutex semaphore so my code is safe, no doubt, but I'd llike to understand this anyway. Thanks for your answer...
Yar
Rails is single threaded. Each request gets it's own controller instance, and only one thread is handled at a time.
cwninja
Okay, sorry to keep going with this: so in a production environment you have "a pack of Mongrels," and one single-threaded controller instance at a time per Mongrel?
Yar
Correct. Many processes (mongrels), one thread per process (rails app).
cwninja
Awesome, thank you again. And my last question... sorry to bother... would a $global Mutex semaphore be shared amongst all mongrel processes?
Yar
Nope, this would be for the single process only. To lock between processes you will need some inter-process flow control, on a single computer you can use file file locks, cross machine database locking (but that's another question entirely).
cwninja