0

Say I have an ActiveRecord model called Person with a table name of people that has id and name columns. There is a unique key constraint on the name column so that no two Person records may share a name. I am using find_or_create_by_name coupled with a rescue block in case two threads try to insert the same value:

def get_or_make_person(name)
  begin
    person = Person.find_or_create_by_name(name)
  rescue ActiveRecord::RecordNotUnique
    retry
  end
  person
end

I am trying to test the thread-safety of my code. So I wrote a test:

name = 'Joe'

t1 = Thread.new do
  person = get_or_make_person name
end

t1 = Thread.new do
  person = get_or_make_person name
end

# Wait until both threads are done
t1.join
t2.join

# At this point, the person should be created no matter what. Let's find him:
person = Person.find_by_name name
assert_not_nil person

However, this test fails. I used Pry to inspect my program after joining both threads, and found that my Person model in the main thread knows absolutely nothing about the record that was created in the other two threads. Looking in the database manually at this point shows that it is definitely in the database:

mysql> select * from people;
+----+------+
| id | name |
+----+------+
| 1  | Joe  |
+----+------+

I even tried using Person.find_by_sql('select * from people'), but that didn't return the record either.

And once I exit my debugging session and try the test again with a different name (say 'Jane'), I am able to retrieve the first record with Person.find_by_name('Joe') or Person.find(1).

What's going on here? Is there any way to somehow force ActiveRecord to reload its knowledge of the people table?

My use case is slightly more involved than this (which is why the example seems slightly contrived), but this should capture the essence of what I am running into.

Additional details:

The exact same code runs successfully and produces the expected results (using a print statement instead of assert_not_nil of course) if run within a standalone script, but fails when run using Test::Unit as a unit test.

Kevin
  • 661
  • 6
  • 14

1 Answers1

0

I just realized that this has already been explained in this question, with a solution found in this answer. Essentially, each test case is performed in a transaction, but the threads create their records outside of that transaction. Turning off transactions for this test case worked for me:

class PersonTest < ActiveSupport::TestCase
  self.use_transactional_fixtures = false
  # ...
end
Community
  • 1
  • 1
Kevin
  • 661
  • 6
  • 14