Как предотвратить возможное состояние гонки - PullRequest
0 голосов
/ 01 мая 2019

Модель My Attendance - это связь между пользователем и событием для записи посещаемости пользователей в данном событии.Любое событие имеет количество доступных мест, которое уменьшается каждый раз, когда пользователь регистрируется на это событие.Ниже приведен контент контроллера посещаемости:

before_action :logged_in_user
before_action :participants_limit, only: :create

def create
  @attendance = current_user.attendances.build(attendance_params)
  if @attendance.save
    @event.decrement!(:seats)
    flash[:success] = "Congratulation! Your participation has been recorded."
    redirect_back(fallback_location: root_url)
  else
    flash[:danger] = @attendance.errors.full_messages.join(', ')
    redirect_back(fallback_location: root_url)
  end
end


private

    def attendance_params
      params.require(:attendance).permit(:event_id)
    end

    def participants_limit
      @event = Event.find(params[:attendance][:event_id])
      if @event.seats == 0
        flash[:danger] = 'Attention! Unfortunately this event is full!'
        redirect_back(fallback_location: root_url)
      end
    end

В предыдущем посте Vasfed предупредил меня, что мой код уязвим для состояния гонки, если два участника попытаются зарегистрироваться одновременновремя.Поэтому я пытаюсь выяснить, как предотвратить состояние гонки.Вероятно, худший сценарий - когда два пользователя пытаются зарегистрироваться для получения последнего доступного места, то есть, когда @event.seats == 1.В этом случае оба пользователя перед началом действия передадут participants_limit и перейдут к действию создания.Интересно, будет ли использование with_lock в этот момент работать или бесполезно:

def create
  @attendance = current_user.attendances.build(attendance_params)
  if @attendance.save
    @event.with_lock do    
      @event.decrement(:seats)
      flash[:success] = "Congratulation! Your participation has been recorded."
      redirect_back(fallback_location: root_url)
    end
  else
    flash[:danger] = @attendance.errors.full_messages.join(', ')
    redirect_back(fallback_location: root_url)
  end
end

Единственная альтернатива, о которой я могу подумать, - это избавиться от before_action и включить его код в действие create какследует:

def create
  @attendance = current_user.attendances.build(attendance_params)
  @event = Event.find(params[:attendance][:event_id])
  @event.with_lock do
    if @event.seats == 0
      flash[:danger] = 'Attention! Unfortunately this event is full!'
      redirect_back(fallback_location: root_url)
    elsif @attendance.save
      @event.decrement(:seats)
      flash[:success] = "Congratulation! Your participation has been recorded."
      redirect_back(fallback_location: root_url)
    else
      flash[:danger] = @attendance.errors.full_messages.join(', ')
      redirect_back(fallback_location: root_url)
    end
  end
end

Я впервые сталкиваюсь с этой проблемой, поэтому у меня нет опыта с пессимистической блокировкой, и я был бы признателен за любые советы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...