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
Performing writes/reads on class variables in Ruby is not thread safe. Performing writes/reads on instance variables appears to be 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.
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.
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.
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With