Lock variants during checkout to avoid race condition

It was possible that several people bought the same variant even though
there wasn't enough stock for everybody. That resulted in negative
stock.
This commit is contained in:
Maikel Linke
2019-08-08 15:33:40 +10:00
parent 9c8c71bd08
commit df2306cf82

View File

@@ -3,6 +3,8 @@ require 'open_food_network/address_finder'
class CheckoutController < Spree::CheckoutController
layout 'darkswarm'
prepend_around_filter :lock_variants, only: :update
prepend_before_filter :check_hub_ready_for_checkout
prepend_before_filter :check_order_cycle_expiry
prepend_before_filter :require_order_cycle
@@ -76,6 +78,33 @@ class CheckoutController < Spree::CheckoutController
private
# We need locks to avoid a race condition on stock checking. Otherwise we end
# up with negative stock when many people check out at the same time. This
# implementation makes a checkout wait for all other checkouts containing one
# of the order`s variants.
#
# There are many places in which stock is stored in the database. Row locking
# on variant level ensures that there are no conflicts even when an item is
# sold through different shops.
#
# Ordering the variants by id prevents deadlocks. Plucking the id sends the
# locking query without building Spree::Variant objects.
def lock_variants
# The before action `load_order` handles this error state:
return yield if current_order.nil?
ActiveRecord::Base.transaction do
# Prevent another checkout from completing the order at the same time:
current_order.lock!
# Prevent any other checkouts of the same line items at the same time:
variant_ids = current_order.line_items.select(:variant_id)
Spree::Variant.where(id: variant_ids).order(:id).lock.pluck(:id)
yield
end
end
def set_default_bill_address
if params[:order][:default_bill_address]
new_bill_address = @order.bill_address.clone.attributes