2012-06-26 54 views
9

我的API允许用户购买某些独特的物品,其中每个物品只能出售给一个用户。因此,当多个用户试图购买相同的物品时,一个用户应该得到回应: ok另一个用户应该得到回应 too_late多线程并发水豚请求?

现在,我的代码中似乎有bug。竞赛条件。如果两个用户同时尝试购买相同的物品,他们都会得到答案 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. 

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

+0

上面的代码示例看起来不像当前的Capybara DSL。它看起来更像是一个使用Rack :: Test的简单控制器测试。那是什么? –

回答

13

多线程和水豚不起作用,因为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,如果case块发生异常。两者都不应该发生。所以通过使用更高的代码,我注意到事情出错的时候。

+0

良好的解决方法!仅仅作为参考,你能发布表现竞赛状态的相关控制器/型号代码吗? –

+0

不能相信这个问题还没有被投票。拯救了我的一天! –

+0

很好的解决方法! Mni thx共享! – ctp

5

您不能同时发出水豚请求。但是,您可以创建多个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' 
+1

我知道顺序请求。我终于自己解决了这个问题。看到我的答案。我**可以**发出并发请求。 – iblue