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