【问题标题】:Concurrent API call in thread in RSpecRSpec中线程中的并发API调用
【发布时间】:2021-11-13 11:09:20
【问题描述】:

我正在尝试通过 RSpec 测试 API 的竞争条件。 我有一个员工表,其中employee_code 需要是唯一的。 如果有多个员工具有相同的代码,系统将自动添加代码与它的工作。现在我想测试当多个相同代码员工尝试同时创建员工时它是否有效。

threads = []
wait = true
4.times do |i|
  threads << Thread.new do
    true while wait
    submit_request example.metadata
  end
end

wait = false
threads.each(&:join)

我为此添加了截断配置

config.around(:each, use_transactional_fixtures: false) do |example|
    config.use_transactional_fixtures = false
    example.run
    config.use_transactional_fixtures = true
  end

我的规格是:

describe "concurrent create employee request", use_transactional_fixtures: false do
  before do
    DatabaseCleaner.strategy = :truncation
    DatabaseCleaner.start
  end

  after do
    DatabaseCleaner.clean
  end

  context "multiple login enabled" do
    context "should save all" do
      let(:params) { { employee: { employee_code: "employee" } } }
      before do
        allow(Time.zone).to receive(:now).and_return(Time.zone.now)
      end

      before do |example|
        threaded_api_request(example)
      end

      it do
        expect(Employee.count).to eq 4
      end
    end
  end
end

但我的问题是虽然所有线程都在执行,但并非所有线程都在调用导致我的规范失败的 API(有时 2 或 3 次)。我是不是做错了什么?

【问题讨论】:

  • 当您的规格失败时,您会得到什么错误?您用来生成唯一代码的代码看起来如何?数据库中是否有唯一索引以避免重复?
  • @spickermann 我得到了预期的 4,得到:1/2/3 我在控制器和线程的代码中设置了一些日志注释。我看到线程评论总是打印 4 次,但控制器中的评论显示 1/2/3 次我正在使用咨询锁以及带有救援块的唯一索引,但我也从不进入救援块

标签: ruby-on-rails ruby rspec


【解决方案1】:

我猜每个 submit_request 都会产生一个单独的线程,所以如果 4 个测试线程完成但某些 submit_request 线程尚未完成,您的测试用例将失败。

      thread1  thread2  thread3  thread4
wait    |         |        |        |
submit  -----------------------------------> request1 r2 r3 r4
        |         |        |        |           |      |  |  |
join    X         x        x        x          done    |  |  done
expect  failed since Employee.count = 2                |  |                                                       

如您所见,请求线程 r2 和 r3 尚未完成,因此只保存了 2 个员工。

所以(如果我猜对了),你应该确保所有 submit_request 线程在预期结果之前完成,如果你的 submit_request 有回调(或者你知道什么时候完成)也许你可以使用 Mutex + ConditionVariable,更优雅的方法是使用CountdownLatch from gem concurrency-ruby(Rails 内部使用这个 gem),你创建一个计数为 4 的 CountDownLatch,然后每当每个请求线程完成时,你减少计数,rspec 期望仅在计数为零。

latch = Concurrent::CountDownLatch.new(4)
4.times do |i|
  Thread.new do
     submit_request(example.metadata) do # done callback
       latch.count_down # -1
     end
  end
end

latch.wait # until latch.count == 0

expect(Employee.count).to eq 4

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-07-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-30
    • 2018-03-08
    相关资源
    最近更新 更多