2012-06-26 81 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