2

运行下面列出的规范时出错。它不会等待线程完成并取消存根迁移方法,从而导致其中一个线程命中实际方法。
注意到它只发生在加载轨道的情况下,没有它们它可以正常工作或者只是更快地完成......

规格:

it "should not allow infinit recursion" do
  runner.stub(total_records: 4)
  runner.stub(:fetch_records_batch).and_return([:one, :two])
  runner.should_receive(:migrate).at_most(100).times
  expect { runner.run }.to raise_error(Exception, /Migration fall into recursion/)
end

it "should pass"
  1.should eq 1
end

提取的一段代码:

class Runner
  def migrate(record)
    raise NotImplementedError
  end

  def run
     while have_records?(records = fetch_records_batch)
       threads = []
       records.each do |record|
         threads << Thread.new(record) do |thread_record|
           begin
             result = migrate(thread_record)
           rescue RuntimeError => exception
             register_event :record_migration_error, thread_record, exception
           end
         end
         recursion_preventer
      end
      threads.each(&:join)
    end
  end

  def recursion_preventer
    @counter ||= 0
    @counter += 1
    raise Exception, "Migration fall into recursion. Check logs." if @counter > (total_records * 1.1).round + 10
  end
end
4

2 回答 2

4

您始终可以模拟 Thread 类以简单地不启动线程,而是运行代码,我发现这比按照您建议的方式重写测试的侵入性要小得多。

将此添加到您的测试类:

describe MyClass do
  before(:each) do
    allow(Thread).to receive(:new).and_yield
  end
  #Your normal unaltered tests under here
end

这种方法的缺陷是您不会遇到竞争条件,因此代码仍然可能包含诸如“线程 a 和线程 b 同时写入同一个变量,导致问题”之类的错误。您还应该考虑死锁场景,因为依赖其他线程来完成某些事情的代码很可能会被这种方法锁定。

于 2014-10-22T11:29:47.877 回答
1

通过添加确保块并调用 threads.each(&:join) 并在 records.each 之后移动 recursion_preventer 来解决

于 2013-04-19T10:35:26.263 回答