mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
154abcab6e
We want to introduce an object-based DSL for building and modifying configuration objects. As part of that we want to make sure that users don't think they can modify configuration_hash values and have them change the configuration. For that reason we're going to freeze the Hash here, and have modified places in tests where we were modifying these hashes. The commit here also adds a test for the Test Databases and in that work we found that we were calling `Rails.env` and Active Record doesn't load Rails. Co-authored-by: John Crepezzi <john.crepezzi@gmail.com>
199 lines
4.9 KiB
Ruby
199 lines
4.9 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "cases/helper"
|
|
|
|
module ActiveRecord
|
|
module ConnectionAdapters
|
|
class ReaperTest < ActiveRecord::TestCase
|
|
class FakePool
|
|
attr_reader :reaped
|
|
attr_reader :flushed
|
|
|
|
def initialize(discarded: false)
|
|
@reaped = false
|
|
@discarded = discarded
|
|
end
|
|
|
|
def reap
|
|
@reaped = true
|
|
end
|
|
|
|
def flush
|
|
@flushed = true
|
|
end
|
|
|
|
def discard!
|
|
@discarded = true
|
|
end
|
|
|
|
def discarded?
|
|
@discarded
|
|
end
|
|
end
|
|
|
|
# A reaper with nil time should never reap connections
|
|
def test_nil_time
|
|
fp = FakePool.new
|
|
assert_not fp.reaped
|
|
reaper = ConnectionPool::Reaper.new(fp, nil)
|
|
reaper.run
|
|
assert_not fp.reaped
|
|
ensure
|
|
fp.discard!
|
|
end
|
|
|
|
def test_some_time
|
|
fp = FakePool.new
|
|
assert_not fp.reaped
|
|
|
|
reaper = ConnectionPool::Reaper.new(fp, 0.0001)
|
|
reaper.run
|
|
until fp.flushed
|
|
Thread.pass
|
|
end
|
|
assert fp.reaped
|
|
assert fp.flushed
|
|
ensure
|
|
fp.discard!
|
|
end
|
|
|
|
def test_pool_has_reaper
|
|
config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", spec_name: "primary")
|
|
pool_config = PoolConfig.new("primary", config)
|
|
pool = ConnectionPool.new(pool_config)
|
|
|
|
assert pool.reaper
|
|
ensure
|
|
pool.discard!
|
|
end
|
|
|
|
def test_reaping_frequency_configuration
|
|
pool_config = duplicated_pool_config(reaping_frequency: "10.01")
|
|
pool = ConnectionPool.new(pool_config)
|
|
|
|
assert_equal 10.01, pool.reaper.frequency
|
|
ensure
|
|
pool.discard!
|
|
end
|
|
|
|
def test_connection_pool_starts_reaper
|
|
pool_config = duplicated_pool_config(reaping_frequency: "0.0001")
|
|
pool = ConnectionPool.new(pool_config)
|
|
|
|
conn, child = new_conn_in_thread(pool)
|
|
|
|
assert_predicate conn, :in_use?
|
|
|
|
child.terminate
|
|
|
|
wait_for_conn_idle(conn)
|
|
assert_not_predicate conn, :in_use?
|
|
ensure
|
|
pool.discard!
|
|
end
|
|
|
|
def test_reaper_works_after_pool_discard
|
|
pool_config = duplicated_pool_config(reaping_frequency: "0.0001")
|
|
|
|
2.times do
|
|
pool = ConnectionPool.new(pool_config)
|
|
|
|
conn, child = new_conn_in_thread(pool)
|
|
|
|
assert_predicate conn, :in_use?
|
|
|
|
child.terminate
|
|
|
|
wait_for_conn_idle(conn)
|
|
assert_not_predicate conn, :in_use?
|
|
|
|
pool.discard!
|
|
end
|
|
end
|
|
|
|
# This doesn't test the reaper directly, but we want to test the action
|
|
# it would take on a discarded pool
|
|
def test_reap_flush_on_discarded_pool
|
|
pool_config = duplicated_pool_config
|
|
pool = ConnectionPool.new(pool_config)
|
|
|
|
pool.discard!
|
|
pool.reap
|
|
pool.flush
|
|
end
|
|
|
|
if Process.respond_to?(:fork)
|
|
def test_connection_pool_starts_reaper_in_fork
|
|
pool_config = duplicated_pool_config(reaping_frequency: "0.0001")
|
|
pool = ConnectionPool.new(pool_config)
|
|
pool.checkout
|
|
|
|
pid = fork do
|
|
pool = ConnectionPool.new(pool_config)
|
|
|
|
conn, child = new_conn_in_thread(pool)
|
|
child.terminate
|
|
|
|
wait_for_conn_idle(conn)
|
|
if conn.in_use?
|
|
exit!(1)
|
|
else
|
|
exit!(0)
|
|
end
|
|
end
|
|
|
|
Process.waitpid(pid)
|
|
assert $?.success?
|
|
ensure
|
|
pool.discard!
|
|
end
|
|
end
|
|
|
|
def test_reaper_does_not_reap_discarded_connection_pools
|
|
discarded_pool = FakePool.new(discarded: true)
|
|
pool = FakePool.new
|
|
frequency = 0.001
|
|
|
|
ConnectionPool::Reaper.new(discarded_pool, frequency).run
|
|
ConnectionPool::Reaper.new(pool, frequency).run
|
|
|
|
until pool.flushed
|
|
Thread.pass
|
|
end
|
|
|
|
assert_not discarded_pool.reaped
|
|
assert pool.reaped
|
|
ensure
|
|
pool.discard!
|
|
end
|
|
|
|
private
|
|
def duplicated_pool_config(merge_config_options = {})
|
|
old_config = ActiveRecord::Base.connection_pool.db_config.configuration_hash.merge(merge_config_options)
|
|
db_config = ActiveRecord::DatabaseConfigurations::HashConfig.new("arunit", "primary", old_config.dup)
|
|
PoolConfig.new("primary", db_config)
|
|
end
|
|
|
|
def new_conn_in_thread(pool)
|
|
event = Concurrent::Event.new
|
|
conn = nil
|
|
|
|
child = Thread.new do
|
|
conn = pool.checkout
|
|
event.set
|
|
Thread.stop
|
|
end
|
|
|
|
event.wait
|
|
[conn, child]
|
|
end
|
|
|
|
def wait_for_conn_idle(conn, timeout = 5)
|
|
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
while conn.in_use? && Process.clock_gettime(Process::CLOCK_MONOTONIC) - start < timeout
|
|
Thread.pass
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|