11

My API を使用すると、ユーザーは特定のユニークなアイテムを購入できます。各アイテムは 1 人のユーザーにのみ販売できます。そのため、複数のユーザーが同じアイテムを購入しようとすると、1 人のユーザーが応答: okを受け取り、もう 1 人のユーザーが応答too_lateを受け取る必要があります。

今、私のコードにバグがあるようです。競合状態。2 人のユーザーが同じアイテムを同時に購入しようとすると、どちらもokという回答が得られます。この問題は、本番環境で明らかに再現可能です。これで、rspec を介してそれを再現しようとする簡単なテストを作成しました。

context "when I try to provoke a race condition" do
  # ...

  before do
    @concurrent_requests = 2.times.map do
      Thread.new do
        Thread.current[:answer] =  post "/api/v1/item/buy.json", :id => item.id
      end
    end

    @answers = @concurrent_requests.map do |th|
      th.join
      th[:answer].body
    end
  end

  it "should only sell the item to one user" do
    @answers.sort.should == ["ok", "too_late"].sort
  end
end

同時にクエリを実行していないようです。これをテストするために、コントローラー アクションに次のコードを追加します。

puts "Is it concurrent?"
sleep 0.2
puts "Oh Noez."

リクエストが並行している場合、予想される出力は次のようになります。

Is it concurrent?
Is it concurrent?
Oh Noez.
Oh Noez.

ただし、次の出力が得られます。

Is it concurrent?
Oh Noez.
Is it concurrent?
Oh Noez.

これは、カピバラのリクエストが同時に実行されるのではなく、一度に 1 つずつ実行されることを示しています。カパバラ リクエストを同時に行うにはどうすればよいですか?

4

2 に答える 2

15

マルチスレッドとカピバラは機能しません。これは、Capabara が接続を順次処理する別のサーバー スレッドを使用するためです。しかし、フォークすればうまくいきます。

プロセス間通信メカニズムとして終了コードを使用しています。より複雑なことを行う場合は、ソケットを使用することをお勧めします。

これは私の簡単で汚いハックです:

before do
  @concurrent_requests = 2.times.map do
    fork do
      # ActiveRecord explodes when you do not re-establish the sockets
      ActiveRecord::Base.connection.reconnect!

      answer = post "/api/v1/item/buy.json", :id => item.id

      # Calling exit! instead of exit so we do not invoke any rspec's `at_exit`
      # handlers, which cleans up, measures code coverage and make things explode.
      case JSON.parse(answer.body)["status"]
        when "accepted"
          exit! 128
        when "too_late"
          exit! 129
      end
    end
  end

  # Wait for the two requests to finish and get the exit codes.
  @exitcodes = @concurrent_requests.map do |pid|
    Process.waitpid(pid)
    $?.exitstatus
  end

  # Also reconnect in the main process, just in case things go wrong...
  ActiveRecord::Base.connection.reconnect!

  # And reload the item that has been modified by the seperate processs,
  # for use in later `it` blocks.
  item.reload
end

it "should only accept one of two concurrent requests" do
  @exitcodes.sort.should == [128, 129]
end

ケースブロックに到達しない場合はプロセスがコード 0 で終了し、例外が発生した場合は 1 で終了するため、私は128129などのかなり風変わりな終了コードを使用します。どちらも起こらないはずです。したがって、より高いコードを使用することで、問題が発生したときに気付くことができます。

于 2012-07-11T20:27:15.203 に答える
8

カピバラのリクエストを同時に行うことはできません。ただし、複数のカピバラ セッションを作成し、それらを同じテスト内で使用して、同時ユーザーをシミュレートすることができます。

user_1 = Capybara::Session.new(:webkit) # or whatever driver
user_2 = Capybara::Session.new(:webkit)

user_1.visit 'some/page'
user_2.visit 'some/page'

# ... more tests ...

user_1.click_on 'Buy'
user_2.click_on 'Buy'
于 2012-07-11T06:02:25.743 に答える