You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

升级至Rails 7.2后RSpec线程内启动嵌套事务时挂起

Rails 7.2测试挂起:线程内嵌套事务死锁的原因解析

问题重现

升级到Rails 7.2.2.1后,这段包含线程内嵌套事务的测试会永久冻结:

context 'error', :transactionless do
  let(:concurrent) do
    Thread.new do
      FlowLead.transaction do
        puts 'inside of the transaction'
      end
    end
  end
  it 'should not hang' do
    puts 'starting outer transaction'
    FlowLead.transaction do
      puts 'starting thread'
      concurrent.join
      puts 'after inner transaction'
    end
  end
end

shared_context 'transactionless' do
    uses_transaction(nil)
    after { DatabaseCleaner.clean_with(:deletion) }
end

测试输出停在starting thread,Postgres的pg_stat_activity显示连接处于idle in transaction状态,最后执行的语句是RELEASE SAVEPOINT active_record_1。关闭RSpec的config.use_transactional_fixtures = false后,测试恢复正常。

根本原因

1. Rails 7.2的连接线程绑定强化

Rails 7.2对数据库连接的线程隔离做了严格限制:

  • 每个数据库连接只能被一个线程持有和操作
  • 嵌套事务依赖Postgres的保存点实现,而保存点必须在当前连接的事务上下文中执行

2. 测试中的循环等待死锁

你的测试代码触发了典型死锁场景:

  • 主线程开启FlowLead.transaction,占用一个数据库连接并进入事务状态
  • 主线程调用concurrent.join,卡住等待子线程执行完毕
  • 子线程启动后,因为uses_transaction(nil)的配置,测试框架没有为它分配独立连接,只能尝试复用主线程的连接
  • Postgres不允许同一连接被多线程同时操作,子线程被阻塞等待连接释放;主线程又在等子线程完成,形成循环等待,最终测试永久挂起

3. 关闭事务性fixtures的修复逻辑

当设置config.use_transactional_fixtures = false后:

  • RSpec不再强制为测试创建全局事务,也不会共享数据库连接
  • 主线程和子线程会从连接池各自获取独立连接
  • 两个线程的事务操作完全隔离,不会出现连接争抢,死锁自然解除

佐证信息

从pg_stat_activity的输出能直接验证问题:

  • 只有一个活跃连接,说明主线程和子线程在争抢同一个连接资源
  • 连接状态为idle in transaction,说明主线程持有事务但未继续执行,正等待子线程,而子线程根本无法获取连接执行事务

内容的提问来源于stack exchange,提问作者Carlos Martinez

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 11:58:13