假设我有一个名为Person
的ActiveRecord模型,其表名为people
,其中包含id
和name
列。 name
列上有一个唯一的键约束,因此没有两个Person
记录可以共享一个名称。我正在使用find_or_create_by_name
和救援块,以防两个线程尝试插入相同的值:
def get_or_make_person(name)
begin
person = Person.find_or_create_by_name(name)
rescue ActiveRecord::RecordNotUnique
retry
end
person
end
我正在尝试测试代码的线程安全性。所以我写了一个测试:
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
但是,此测试失败。我在加入两个线程后使用Pry来检查我的程序,并发现主线程中的Person
模型对于在其他两个线程中创建的记录一无所知。此时手动查看数据库表明它肯定在数据库中:
mysql> select * from people;
+----+------+
| id | name |
+----+------+
| 1 | Joe |
+----+------+
我甚至尝试使用Person.find_by_sql('select * from people')
,但这也没有返回记录。
一旦我退出调试会话并使用其他名称再次尝试测试(说“简”),我就可以使用Person.find_by_name('Joe')
或Person.find(1)
检索第一条记录。
这里发生了什么?有没有办法以某种方式强制ActiveRecord重新加载其people
表的知识?
我的用例比这更具参与性(这就是为什么这个例子看起来有点人为),但这应该捕捉到我遇到的内容的本质。
其他详情:
完全相同的代码成功运行并产生预期的结果(当然使用print语句而不是assert_not_nil
)如果在独立脚本中运行,但在使用Test :: Unit作为单元测试运行时失败。
答案 0 :(得分:0)
我刚刚意识到这已经在this question中解释过,并在this answer中找到了解决方案。实质上,每个测试用例都在事务中执行,但线程在该事务之外创建其记录。关闭此测试用例的事务对我有用:
class PersonTest < ActiveSupport::TestCase
self.use_transactional_fixtures = false
# ...
end