Как избежать состояния гонки при создании дочернего идентификатора на основе родительского атрибута - PullRequest
1 голос
/ 24 марта 2020

Представьте, что у вас есть Site, в котором есть одна из тех вещей, которые "берут число", где люди берут число, а затем ждут своей очереди. Допустим, каждое число является Order. Отношение состоит в том, что у Site есть много Orders

. В заданной точке сброса менеджер сайта проходит и заменяет вещи «возьми число» новыми бросками чисел, т. Е. Вы могу представить, что это происходит ежедневно. Тем не менее, вещи типа «взять число» изготавливаются с конечными числами, поэтому каждый бросок состоит, скажем, из 1-100, а затем начинается следующий бросок 100-999.

Я пытаюсь смоделировать описанное выше поведение, как я думал о приближении к нему:

  1. На родительском элементе Site есть атрибут start_number. Нажатие на сброс / переключение броска приведет к сбросу start_number на 100, т. Е. К первому номеру броска
  2. На дочернем элементе Order существует обратный вызов, который присваивает номер. Если родительский номер Site равен 100, то это означает, что это первый Order после сброса, как описано в шаге # 1, поэтому тогда это число также равно 100. Теперь родительский элемент Site автоматически обновляется, так что он больше не находится в состоянии сброса (например, start_number больше не равен 100). Для будущего Orders назначенный номер - это просто следующий номер после предыдущего заказа

Вот код:

class Site
  has_many :orders
end

class Order
  belongs_to :site

  before_save :assign_number

  def assign_number
    if site.start_number == 100
      self.number = 100
      self.site.update_column(:start_number, nil)
    else
      self.number = self.site.orders.where.not(number:nil).last.number + 1
    end
  end
end

Но это дерьмо, потому что в отличие от реального мира «возьми число», 2 ордера могут быть обработаны одновременно, нет ограничения unique на Order.number, потому что числа снова используются (бросок сбрасывается). Но, очевидно, бесполезно, если после сброса 2 ордера, которые размещаются близко друг к другу, равны 100. Вы хотите, чтобы несколько ордеров совместно использовали number, если действительно произошло событие сброса, а не только случайное время.

Еще одна проблема в этом подходе - когда за 1 ордером следует вторая. Например, скажем, последний присвоенный номер был 415, 2 заказа выполняются в быстрой последовательности. Первому присваивается 416, второму так близко, что self.site.orders.where.not(number:nil).last.number все еще возвращает 415 (т. Е. 416 еще не сохранено), и поэтому второму ордеру теперь также присваивается 416.

Было бы здорово получить идеи о том, как лучше смоделировать желаемое поведение. Спасибо!

ОБНОВЛЕНИЕ По комментариям @ Фернана, я собираюсь go с блокировкой пессимистичности c, которую я реализую согласно примечаниям здесь . Итак, прямо сейчас код выглядит так:

  def assign_number
    site = self.site.lock!
    if site.start_number == 100
      self.number = 100
      site.start_number = nil
    else
      last_order = self.site.orders.where.not(number:nil).last.lock!
      self.number = last_order.number + 1
      last_order.save! # releases lock
    end
    site.save! #releases lock, whether or not call number was updated to nil
  end

Я не совсем уверен, как написать c, хотя ... так как написание spe c по определению упорядочено ... как вы заставить 2 приказа сохранить близко друг к другу, чтобы смоделировать это поведение?

1 Ответ

0 голосов
/ 24 марта 2020

Один из способов - создать другую таблицу, в которой будет храниться последний номер.

#code   :string   not null
#number :bigint   default(0), not null

class Serial < ApplicationRecord
  def self.get_latest_number
    Serial.transaction do
      self.lock.find_or_create_by!(code: 'just_any_identification').increment!(:number).count
    end
  end
end

, а затем установить номер наиболее вероятно во время обратного вызова.

class Order
  before_create :set_number #or after_create

  private
    def set_number
      self.number=Serial.get_latest_number if self.number.blank?
    end
end

принять к сведению блокировка транзакции .

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