Резервные действия контроллера 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).