1

Rails アプリに、、、およびモデルがUserありますOrder。と の間には多対多の関係があり、 と と の間には1対多の関係があります。ContestPositionUserOrderPositionOrderUserContest

execute注文を処理するメソッドを備えた注文処理サービスがあります。競合状態を防ぐために、lock!内部でトランザクションを使用します。

問題は、引数の順序が同じである 2 つ以上のexecuteメソッドを同時に呼び出すと、デッドロックが発生することがあることです。最も奇妙なことは、最初のメソッドが実行を開始し、2 番目のメソッドがロック解除の命令を待っていることです。すべて期待どおりです。しかし、私の最初の方法(実行中)は途中でロックしています。さらに奇妙なのは、ロックされたレコードが処理されていない行でメソッドがロックされていることです。さらに、関連のない行をコメントすると、メソッドが永久に実行されている行がランダムに変更される可能性があり、変更されていない行、またはロックされたレコードを使用していない行がロックされている場合もあります (常にではありません)。

これが私のexecute方法です

  user = order.user
  contest = user.contest
  ActiveRecord::Base.transaction do

    # the second method awaiting here as excpected
    order.lock! 

    # some processing stuff here like
    order.executed = true

    position = find_or_create_position(order)
    position.lock!

    # .where.not(id: order.id) just in case to ignore locked row
    # but based on business logic it can't really be anyway.
    #
    # Plus it is not seems like the reason because 
    # if I comment this and the next lines deadlock occurs later
    open_orders = user.orders.open.where.not(id: order.id)

    # commonly method stops here
    closed_orders = close_open_orders!(order, price, open_orders)

    # method stops here if I comment two lines above
    order.save!

    # I left this line because as I remember once 
    # after some random changes like commenting some lines 
    # I got method stopped even here
    commission = contest.commission * order.count
    closed_orders.each { |closed_order| closed_order.save! }

    # here some position processing and finally
    position.save!

    user.lock!
    # and user processing
    user.save!
  end

コメントで処理するとは、単に属性をレコードに割り当てることを意味します。

だからここに私のclose_open_orders!方法の始まりです

  rest_count = order.count

  # here it stops. each just stops without any iteration. 
  open_orders.each do |open_order|

eachしかし、私が言ったように、このメソッドを無視するとデッドロックが発生するため、問題はステートメントにあるとは思いません。

そして、1 つのメソッドのみが実行されている場合、すべてが正常に機能します。pg ログを添付することもできますが、デッドロックは見られません。更新の注文が 2 つ選択されています。選択するよりも、user.orders.open.where.not(id: order.id)それだけです。その後は関係ありません(または関係ありませんか?):

03:58:13 [21231-1] LOG:  statement: set client_encoding to 'UTF8'
03:58:13 [21231-2] LOG:  statement: set client_encoding to 'unicode'
03:58:13 [21231-3] LOG:  statement: SET client_min_messages TO 'warning'

必要に応じて、さらに pg ログを追加できます。

UPD sidekiqを使用してジョブを非同期に実行しています

4

0 に答える 0