Stale connection in `DatabaseCleaner::ActiveRecord::Truncation#connection`
Nobody has claimed this yet.
Assessment
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Newbie friendliness
- 35/100
Research direction
Start at DatabaseCleaner::ActiveRecord::Truncation#clean and the strategy's @connection initialization described in the issue. Reproduce the connection-pool scenario with the provided RSpec example, then verify that repeated cleaning obtains a usable connection and that the tests no longer raise ActiveRecord::ConnectionNotEstablished.
Written by the indexing model from the issue text.
Description
Summary
Each instance of the DatabaseCleaner::ActiveRecord::Truncation strategy initializes its @connection field only once, using the ActiveRecord default connection. When the connection is first requested, ActiveRecord checks it out of the connection pool. Various hooks (e.g. ActiveRecord::TestFixtures.teardown_fixtures) can return the connection to the pool, but Truncation is unaware of this and holds onto the connection object.
If another thread checks the same connection out of the pool and calls disconnect!, then when Truncation tries to use the connection to clean the database, it will find that the connection is closed and raise an error.
Steps to reproduce:
-
In a Rails/PostgreSQL project using RSpec, configure
DatabaseCleaneras follows:RSpec.configure do |config| config.before(:suite) do DatabaseCleaner.strategy = :truncation end config.around do |example| DatabaseCleaner.cleaning do example.run end end end -
Write a test that, in a background thread, checks a connection out of the pool, removes it from the pool, and disconnects it, e.g.
describe 'connection pooling' do def do_disconnect Thread.new do connection_pool = ActiveRecord::Base.connection_pool connection = connection_pool.checkout.tap do |conn| connection_pool.remove(conn) end begin connection.execute('SELECT 1') ensure connection.disconnect! end end end 5.times do |i| it "test #{i}" do ActiveRecord::Base.connection.execute('SELECT 1') sleep(0.5) do_disconnect if i % 2 == 0 end end end(This example is obviously quite contrived; I ran into the problem in a more realistic situation. See discussion in https://github.com/bensheldon/good_job/issues/849.)
Expected
- Tests pass.
Actual
- First couple of tests pass
- Subsequent tests fail with
ActiveRecord::ConnectionNotEstablished: connection is closedraised fromDatabaseCleaner.cleaningviaTruncation#clean
Workaround
Instead of using DatabaseCleaner.cleaning in an around block, explicitly call DatabaseCleaner.clean_with(:truncation) in an after(:each) block:
RSpec.configure do |config|
config.after(:each) do
DatabaseCleaner.clean_with(:truncation)
end
end
Proposed fix
Get a fresh connection in each call to Truncation.clean -- I simulated this with a prepended module and it seems to work:
module Cleaninator
def clean
@connection = nil
super
end
end
class DatabaseCleaner::ActiveRecord::Truncation
prepend Cleaninator
end
- Dominant language
- Ruby
- Stars
- 76
- Forks
- 76
- PR merge metrics
- No merged PRs in 30d
Getting set up
- Ships a Dockerfile or Docker Compose file
- No pull request template
- No contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from DatabaseCleaner/database_cleaner-active_record
-
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
-
Difficulty 5/5 Over a week Newbie friendliness 30/100
DatabaseCleaner/database_cleaner-active_record#128 · 1 comment ·
-
Difficulty 3/5 1-2 days Newbie friendliness 35/100
DatabaseCleaner/database_cleaner-active_record#127 · 3 comments ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 45/100
-
Difficulty 5/5 Over a week Newbie friendliness 25/100
All issues in DatabaseCleaner/database_cleaner-active_record
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 86/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
simp/pupmod-simp-stunnel#173 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
TheOdinProject/curriculum#31444 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100