решение для состояния гонки в рельсах (ограниченное количество X в Y) - PullRequest
3 голосов
/ 02 марта 2012

Редактировать: Использование MySQL ...

Скажем, у вас есть приложение, которое добавляет учеников в класс, и этот класс имеет ограниченное пространство ... так что вы делаете что-то вроде этого:

def add
  if some_classroom.size < MAX_SIZE
    add_student_to_class
  end
end

Это состояние гонки в многопоточной среде. Lame.

Предположим, что мы

  1. не хочу этого, и
  2. не хочет блокировать нашу таблицу или запись в классе (что приводит к тому, что наше приложение сосет в другом месте)

Что мы делаем?

Я предлагаю это:

class Classroom < ActiveRecord::Base
  has_one :classroom_lock
  after_create :create_lock_record

  def create_lock_record
    c = ClassroomLock.new
    c.classroom = self
    c.save!
  end
end

class ClassroomLock < ActiveRecord::Base
  belongs_to :classroom
end

def add
  c = Classroom.first
  ActiveRecord::Base.transaction do
    c.classroom_lock.lock!
    c = Classroom.first #load this again (it might have changed)
    if c.size < MAX_SIZE
      c.add_new_student(some_student)
    else
      do_stuff_about_not_enough_room
    end
  end
end

Кажется, это должно работать потрясающе. Мой (вымышленный) метод Classroom # show не блокируется, поскольку запись в классе фактически не заблокирована, а метод add фактически является однопоточным, поскольку любые дополнительные процессы будут вынуждены ожидать блокировки! линии до снятия блокировки.

Это работает? Может быть? Я думаю так? Я не знаю ...

Я приложил немало усилий для одновременной работы с несколькими процессами, но трудно точно знать (в конце концов, это условие гонки).

Может ли кто-нибудь предоставить какую-то дополнительную информацию?

1 Ответ

0 голосов
/ 09 ноября 2012

Привет,

Я представляю довольно простой SQL-подход только с одним предупреждением: добавьте дополнительный столбец, называемый чем-то вроде list_position, и добавьте уникальный индекс в таблицу отношений класса-студента, состоящую изclassroom_id, student_id и list_position.

Вставка, что происходит, путем установки list_position числа учащихся в классе плюс один.Код Ruby, вероятно, будет где-то рядом

classroomStudent = ClassroomStudent.new
classroomStudent.list_position = classroom.size + 1
until classroomStudent.save
  classroomStudent.list_position += 1
  do_stuff_about_not_enough_room if classroomStudent.list_position > MAX_SIZE
end

Результат: если две вставки попытаются одновременно вставить учащегося в один и тот же класс, уникальный индекс не даст учиться одному ученику (INSERT не удалось).Это означает, что вам, возможно, придется повторить попытку до list_position=MAX_SIZE, но гарантировано, что у вас никогда не будет слишком много учеников в классе.Вы также можете использовать этот подход для создания очереди ожидания.

Если у вас уже есть данные в вашей реляционной таблице, вам сначала нужно будет добавить значение для list_position.Я бы предположил, что что-то вроде

UPDATE ClassroomStudents AS c1 SET c1.list_position = COALESCE((SELECT MAX(c2.list_position) FROM ClassroomStudents AS c2 WHERE c2.classroom_id = c1.classroom_id), 0) + 1;

сделало бы эту работу здесь, хотя это могло бы быть немного медленной стороной.

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

С уважением

TC

...