Rails线程调用record.save!后休眠问题求助(RSpec测试场景)
record.save!出现休眠问题的解决思路 问题场景
我在RSpec测试里使用线程时遇到了一个棘手的问题:在线程内调用record.save!的时候,线程居然进入了休眠状态。我的代码示例是这样的:
a = Thread.new do t = User.find(1) t.role = 2 t.save! end
控制台输出的信息显示线程处于sleep状态,同时数据库操作看起来已经完成了:
Thread:0x007fe1d68d9978@(pry):125 sleep
[32] pry(main)> User Load (8.5ms) SELECTusers.* FROMusersWHEREusers.id= 1 LIMIT 1
(0.1ms) BEGIN
(0.2ms) COMMIT
问题原因分析
这种情况大多和RSpec的事务隔离机制脱不了干系。RSpec默认会给每个测试用例套一层数据库事务,测试结束后回滚事务来保证测试环境的干净。但线程是独立的执行上下文,它不会共享主线程的数据库事务上下文——当主线程的事务还没提交时,线程里的数据库操作看似执行完毕,实则卡在了事务锁或者等待主线程事务状态的环节,最终导致线程进入休眠。
另外,数据库连接池配置不足也可能是诱因:如果RSpec的数据库连接池设置的连接数不够,线程尝试获取新连接时会被阻塞,进而表现为线程休眠。
解决方案
这里有几个可行的解决办法,你可以根据自己的场景选择:
禁用RSpec的事务测试策略:把测试的事务策略改成
:truncation或者:deletion,这样每个测试用例结束后会直接清空数据库表,而非依赖事务回滚。你可以在spec_helper.rb或者rails_helper.rb里这么配置:RSpec.configure do |config| config.use_transactional_fixtures = false config.before(:suite) do DatabaseCleaner.strategy = :truncation end config.before(:each) do DatabaseCleaner.start end config.after(:each) do DatabaseCleaner.clean end end这种方式相比事务会慢一些,但能彻底避免线程和事务上下文冲突的问题。
给线程手动分配数据库连接:在线程内部显式获取一个数据库连接,确保线程拥有独立的连接上下文。修改你的线程代码:
a = Thread.new do ActiveRecord::Base.connection_pool.with_connection do t = User.find(1) t.role = 2 t.save! end end # 别忘了等待线程执行完成 a.join这样线程会从连接池里拿到专属连接,不会和主线程的事务上下文互相干扰。
调整数据库连接池大小:如果是连接池不足的问题,你可以在
config/database.yml里增大连接池的容量,比如:test: adapter: mysql2 # 或者你的数据库适配器 database: app_test pool: 10 # 调大到足够线程使用的数量
额外提示
在RSpec里使用线程的时候,一定要记得调用thread.join等待线程执行完成,不然主线程可能已经结束测试了,线程还没跑完,导致你看不到执行结果或者出现奇怪的状态。
内容的提问来源于stack exchange,提问作者Punny

