【问题标题】:Multi-threaded concurrent Capybara requests?多线程并发 Capybara 请求?
【发布时间】:2012-06-26 11:59:00
【问题描述】:

我的 API 允许用户购买某些独特的物品,其中每件物品只能卖给一个用户。因此,当多个用户尝试购买同一商品时,一个用户应该得到响应:ok,另一个用户应该得到响应too_late

现在,我的代码中似乎存在错误。比赛条件。如果两个用户同时尝试购买相同的商品,他们都会得到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.

这告诉我,capybara 请求不会同时运行,而是一次运行一个。 如何使我的 capabara 请求并发?

【问题讨论】:

  • 您上面的代码示例在我看来不像当前的 Capybara DSL。它看起来更像是使用 Rack::Test 进行的普通控制器测试。是这样的吗?

标签: ruby-on-rails rspec capybara


【解决方案1】:

多线程和 capybara 不起作用,因为 Capabara 使用单独的服务器线程来按顺序处理连接。但如果你分叉,它会起作用。

我使用退出代码作为进程间通信机制。如果你做更复杂的事情,你可能想要使用套接字。

这是我快速而肮脏的 hack:

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

我使用像 128129 这样的非常奇特的退出代码,因为如果没有到达 case 块,进程会以代码 0 退出,如果发生异常则以代码 1 退出。两者都不应该发生。因此,通过使用更高级别的代码,我会在出现问题时注意到。

【讨论】:

  • 很好的解决方法!仅供参考,能否贴出体现竞态条件的相关控制器/模型代码?
  • 不敢相信这个问题和答案还没有被投票。拯救了我的一天!
  • “多线程和水豚不起作用” 在撰写本文时这可能是正确的,但现在非常错误。 Capybara 在线程环境中工作得很好,只要您为每个线程显式初始化一个单独的 Capybara::Session 对象,并使用该对象而不是默认会话(即,而不是仅仅说visit url@capybara.visit url)。请参阅下面 Tanzeeb 的回答。
【解决方案2】:

您不能同时发出 capybara 请求。但是,您可以创建多个 capybara 会话并在同一个测试中使用它们来模拟并发用户。

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'

【讨论】:

  • 我知道顺序请求。我终于自己解决了这个问题。看我的回答。我可以发出并发请求。
猜你喜欢
  • 2021-11-30
  • 2016-11-05
  • 2019-05-01
  • 2012-02-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多