:transaction strategy: uncommitted data disappears after perform_enqueued_jobs on Rails ≥ 7.2
还没有人认领这个 Issue。
评估
- 难度
- 3/5
- 预计耗时
- 1-2 天
- 新手友好度
- 67/100
调研方向
Start at the :transaction strategy entry point and read the reproduction app's README, then run for i in {1..100}; do bin/rails test; done to observe the intermittent failure. Compare the strategy's connection setup and teardown with Rails' pinned transactional fixtures; done means the reproduction completes without RecordNotFound or lock-wait failures.
由索引模型根据 Issue 内容生成。
描述
Below is summary of bug I have found in my company's app I work for. It is related to how database cleaner and rails internals handles the connections to database. I used AI to help investigating the problem but I verified (as far as I understand it) and reproduced the problem. Below is the summary of but I prepared a detailed rails app (see its' README) with instruction how to reproduce it https://github.com/radarek/database-cleaner-connection-bug.
Summary
On Rails ≥ 7.2, the :transaction strategy can intermittently cause ActiveRecord::RecordNotFound (and lock-wait timeouts on writes): a row created inside a test becomes invisible after an ActiveJob runs inline (perform_enqueued_jobs).
Reproduction app: https://github.com/radarek/database-cleaner-connection-bug (Rails 8.1, MySQL/trilogy, Minitest). for i in {1..100}; do bin/rails test; done should reproduce the problem at least once
Cause
The :transaction strategy opens its wrapping transaction on a leased connection:
connection = ActiveRecord::Base.lease_connection
connection.begin_transaction(joinable: false)
Running any ActiveJob wraps execution in Rails.application.reloader.wrap { … }. On completion, Rails' AR executor hook (ConnectionPool::ExecutorHooks.complete) sees a connection whose transaction is not joinable and releases it back to the pool — while it still holds the open transaction and all of the test's uncommitted rows.
The next query then leases a connection again. If the pool hands it a different physical connection (e.g. after the background Reaper reclaims a connection and reorders the LIFO free list), that connection is a separate DB session that can't see the uncommitted data → RecordNotFound. This is why it's intermittent and why it only shows up alongside other specs that grow the pool past one connection.
Suggested fix
On Rails ≥ 7.2, pin the connection instead of leasing it:
pool.pin_connection!(...) # setup
pool.unpin_connection! # teardown
A pinned connection is immune to the executor hook (checkout always returns the pinned connection), so the swap can't happen. This is exactly what Rails' own transactional fixtures do — and the pinned variant in the repro app passes 100% of runs.
Full mechanism write-up (LIFO free list, orphaned connections, Reaper timing) is in the repo README.
- 主要语言
- Ruby
- 星标
- 3k
- 派生
- 485
- PR 合并指标
- 30 天内没有已合并 PR
环境准备
- 提供 Dockerfile 或 Docker Compose 文件
- 没有 Pull Request 模板
- 没有贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
DatabaseCleaner/database_cleaner 的其他 Issue
-
难度 4/5 3-5 天 新手友好度 25/100
-
难度 3/5 1-2 天 新手友好度 45/100
-
难度 4/5 3-5 天 新手友好度 35/100
DatabaseCleaner/database_cleaner#717 · 5 条评论 · 1 个 reaction ·
-
难度 4/5 3-5 天 新手友好度 25/100
DatabaseCleaner/database_cleaner#716 · 2 条评论 ·
-
难度 5/5 一周以上 新手友好度 10/100
DatabaseCleaner/database_cleaner#705 · 1 条评论 · 5 个 reaction ·
查看 DatabaseCleaner/database_cleaner 的全部 Issue
相似的 Issue
-
bug
难度 2/5 1-3 小时 新手友好度 62/100
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 65/100
slovensko-digital/autogram-portal#392 ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 65/100
voxpupuli/puppet-quadlets#122 · 5 条评论 ·
维护者通常 1 天内回复
-
难度 1/5 1 小时以内 新手友好度 82/100
TheOdinProject/curriculum#31452 ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 70/100