Jesper Lugner
Jesper Lugner

Reputation: 195

Testing rails for race conditions, and cleaning up afterwards

I'm trying to test parts of my code for race conditions. The issue I had was related to uniqueness validations, which as it turns out is not safe from race conditions in rails. I believe I'll be able to fix the issue, but I'm not sure how to test my solution.

The closest I've come is the following(inspired by: http://blog.arkency.com/2015/09/testing-race-conditions/):

test "Can't create duplicate keys with same value and keyboard" do
  assert_equal(5, ActiveRecord::Base.connection.pool.size)
  begin
    concurrency_level = 4
    keyboard = create :keyboard
    should_wait = true

    statuses = {}

    threads = Array.new(concurrency_level) do |i|
      Thread.new do
        true while should_wait
        begin
          # Unique validation for key values exists scoped to keyboard
          key = keyboard.keys.new(value: 'a')
          statuses[i] = key.save
        rescue ActiveRecord::RecordNotUnique
          statuses[i] = false
        end
      end
    end
    should_wait = false
    threads.each(&:join)

    assert_equal(1, keyboard.keys.count)
    assert_equal(1, statuses.count { |_k, v| v })
    assert_equal(3, statuses.count { |_k, v| !v })
  ensure
    ActiveRecord::Base.connection_pool.disconnect!
  end
end

The code above is structured exactly like mine, but models have changed to be more general.

The test itself seems to work alright. However, keys created in the tests are not deleted afterwards. I'm using DatabaseCleaner, and I've tried all different strategies. Also, sometimes I get a Circular Dependency issue for constant Key. Not sure why, but I'm guessing its due to requires not being thread safe in ruby?

Is there a better way to my problem? As I specified above, I've gotten a few different issues with this, and I feel it should be a common enough problem that good testing standards should exist.

Upvotes: 1

Views: 2194

Answers (1)

Brad
Brad

Reputation: 738

A few things:

1) Probably my ignorance, but the true while should_wait line seems wrong to me. Something more like while should_wait do seems more like what you intend. You also call pod.save which seems to not make sense, so I'm guessing this is not exactly the code you're using.

2) I would expect database cleaner to work, because I think if you use the "truncation" strategy it will go through and truncate every table when the test is run. My wild ass guess is that you have configured it to only run for integration tests and this is a unit test, or something like that. If that's not it, try calling DatabaseCleaner.truncate (or however you do that explicitly) at the end of the test and see if that works.

3) Can you solve the problem with a unique index in your DB? That removes the need for this test at all because you get to just trust your database. When you do get a non-unique value you can handle that in a non-validation way in your code. Much faster too, because you don't have to make the extra sql call every time you save.

4) Impossible to know from the information given why you're getting the circular dependency issue. I've had that issue before and did a puts caller at the top of the file to try to diagnose.

Upvotes: 1

Related Questions