Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Thread-safe Rails controller actions - setting instance variables?

I have to write a threaded Rails app because I am running it atop of Neo4j.rb, which embeds a Neo4j graph database inside the Rails process, and thus I have to serve multiple requests from the same process. Yeah, it'd be cool if connecting to a Neo4j database worked like SQL databases, but it doesn't, so I'll quit complaining and just use it.

I'm quite worried about the implications of writing concurrent code (as I should be), and just need some advice on how to handle common a common scenario - a controller sets an instance variable or a variable in the session hash, then some stuff happens. Consider the following crude code to demonstrate what I mean:

# THIS IS NOT REAL PRODUCTION CODE
# I don't do this in real life, it is just to help me ask my question, I
# know about one-way hashing, etc.!

class SessionsController
  def create
    user = User.find_by_email_and_password(params[:email], params[:password])
    raise 'auth error' unless user
    session[:current_user_id] = user.id
    redirect_to :controller => 'current_user', :action => 'show'
  end
end

class CurrentUserController
  def show
    @current_user = User.find(session[:current_user_id])
    render :action => :show # .html.erb file that uses @current_user
  end
end

The question: Are there any race conditions in this code?

In SessionsController, are the session hash and the params hash thread-local? Say the same browser session makes multiple requests to /sessions#create (to borrow Rails route syntax) with different credentials, the user that is logged in should be the request that hit the line session[:current_user_id] = user.id last? Or should I wrap a mutex lock around the controller action?

In the CurrentUserController, if the show action is hit simultaneously by two requests with different sessions, will the same @current_user variable be set by both? I.e. will the first request, as it is processing the .html.erb file, find that it's @current_user instance variable has suddenly been changed by the second thread?

Thanks

like image 630
Asfand Qazi Avatar asked Aug 17 '12 10:08

Asfand Qazi


People also ask

Are instance variables thread-safe in Ruby?

Performing writes/reads on class variables in Ruby is not thread safe. Performing writes/reads on instance variables appears to be thread safe.

Why instance variables are not thread-safe?

It is because, Servlets create only one instance and multiple threads access it. So in that case Instance Variables are not thread safe.

What is an instance variable in Rails?

In Rails, instance variables (like @books ), are used to share data between your controller & views. But you can still use them normally, for your own classes.


2 Answers

Each request gets a new instance of your controller. As a consequence controller instance variables are thread safe. params and session are also backed by controller instance variables (or the request object itself) and so are also safe.

like image 103
Frederick Cheung Avatar answered Oct 14 '22 20:10

Frederick Cheung


It's important to know what is shared between threads and what isn't.

Now back to your specific example. Two requests hit CurrentUserController#show simultaneously, hence they are handled by two concurrent threads. The key here is that each thread has its own instance of CurrentUserController, so there are two @current_user variables which don't interfere. So there's no race condition around @current_user.

An example of race condition would be this:

class ApplicationController < ActionController::Base
  before_each :set_current_user
  cattr_accessor :current_user

  def set_current_user
    self.class.current_user = User.find_by_id(session[:current_user_id])
  end
end

# model
class LogMessage < ActiveRecord::Base
  belongs_to :user

  def self.log_action(attrs)
    log_message = new(attrs)
    log_message.user = ApplicationController.current_user
    log_message.save
  end
end

On more general note, because of GIL (Global Interpreter Lock) benefits from using threads in MRI ruby are rather limited. There are implementation which are free from GIL (jruby).

like image 23
Serge Balyuk Avatar answered Oct 14 '22 19:10

Serge Balyuk