Резервные действия контроллера Rails - установка переменных экземпляра?
Мне нужно написать потоковое приложение Rails, потому что я запускаю его поверх Neo4j.rb, который включает в себя базу данных диаграммы Neo4j внутри процесса Rails, и поэтому я должен обслуживать несколько запросов из одного и того же процесса. Да, было бы здорово, если бы соединение с базой данных Neo4j работало подобно базам данных SQL, но это не так, поэтому я перестану жаловаться и просто его использовать.
Я очень обеспокоен последствиями написания параллельного кода (как и должно быть), и мне просто нужно дать совет о том, как обрабатывать общий общий сценарий - контроллер задает переменную экземпляра или переменную в хеш сессии, то происходит кое-что. Рассмотрим следующий грубый код, чтобы продемонстрировать, что я имею в виду:
# 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
Вопрос: Существуют ли какие-либо условия гонки в этом коде?
В SessionController есть хеш session
и params
хэш-поток-локальный? Скажем, что тот же сеанс браузера делает несколько запросов /session # create (заимствовать синтаксис Rails-маршрута) с разными учетными данными, пользователь, который вошел в систему, должен быть запросом, который попадает в строку session[:current_user_id] = user.id
последним? Или я должен обернуть блокировку мьютекса вокруг действия контроллера?
В CurrentUserController, если действие show одновременно попадает в два запроса с разными сеансами, будет ли установлена одна и та же переменная @current_user обоими? То есть будет первый запрос, поскольку он обрабатывает файл .html.erb, обнаруживает, что переменная экземпляра @current_user неожиданно была изменена вторым потоком?
Спасибо
Ответы
Ответ 1
Каждый запрос получает новый экземпляр вашего контроллера. Как следствие, переменные экземпляра контроллера являются потокобезопасными. params
и session
также поддерживаются переменными экземпляра контроллера (или самим объектом запроса), поэтому они также безопасны.
Ответ 2
Важно знать, что общего между потоками, а что нет.
Теперь вернемся к вашему конкретному примеру. Два запроса нажимают CurrentUserController#show
одновременно, поэтому они обрабатываются двумя параллельными потоками. Ключ здесь состоит в том, что каждый поток имеет свой собственный экземпляр CurrentUserController
, поэтому есть две переменные @current_user
, которые не мешают. Поэтому нет условий гонки вокруг @current_user
.
Примером состояния гонки было бы следующее:
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
В более общем случае из-за использования GIL (Global Interpreter Lock) преимущества использования потоков в рубине МРТ весьма ограничены. Есть реализация, свободная от GIL (jruby).