aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/test
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/test')
-rw-r--r--activerecord/test/cases/connection_adapters/adapter_leasing_test.rb6
-rw-r--r--activerecord/test/cases/connection_adapters/connection_handler_test.rb46
-rw-r--r--activerecord/test/cases/connection_management_test.rb23
-rw-r--r--activerecord/test/cases/connection_pool_test.rb172
-rw-r--r--activerecord/test/cases/primary_keys_test.rb8
-rw-r--r--activerecord/test/cases/type/integer_test.rb2
6 files changed, 227 insertions, 30 deletions
diff --git a/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb b/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb
index 662e19f35e..580568c8ac 100644
--- a/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb
+++ b/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb
@@ -6,7 +6,7 @@ module ActiveRecord
class Pool < ConnectionPool
def insert_connection_for_test!(c)
synchronize do
- @connections << c
+ adopt_connection(c)
@available.add c
end
end
@@ -24,7 +24,9 @@ module ActiveRecord
def test_lease_twice
assert @adapter.lease, 'should lease adapter'
- assert_not @adapter.lease, 'should not lease adapter'
+ assert_raises(ActiveRecordError) do
+ @adapter.lease
+ end
end
def test_expire_mutates_in_use
diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
index b72f8ca88c..9b1865e8bb 100644
--- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb
+++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
@@ -46,6 +46,52 @@ module ActiveRecord
def test_connection_pools
assert_equal([@pool], @handler.connection_pools)
end
+
+ if Process.respond_to?(:fork)
+ def test_connection_pool_per_pid
+ object_id = ActiveRecord::Base.connection.object_id
+
+ rd, wr = IO.pipe
+ rd.binmode
+ wr.binmode
+
+ pid = fork {
+ rd.close
+ wr.write Marshal.dump ActiveRecord::Base.connection.object_id
+ wr.close
+ exit!
+ }
+
+ wr.close
+
+ Process.waitpid pid
+ assert_not_equal object_id, Marshal.load(rd.read)
+ rd.close
+ end
+
+ def test_retrieve_connection_pool_copies_schema_cache_from_ancestor_pool
+ @pool.schema_cache = @pool.connection.schema_cache
+ @pool.schema_cache.add('posts')
+
+ rd, wr = IO.pipe
+ rd.binmode
+ wr.binmode
+
+ pid = fork {
+ rd.close
+ pool = @handler.retrieve_connection_pool(@klass)
+ wr.write Marshal.dump pool.schema_cache.size
+ wr.close
+ exit!
+ }
+
+ wr.close
+
+ Process.waitpid pid
+ assert_equal @pool.schema_cache.size, Marshal.load(rd.read)
+ rd.close
+ end
+ end
end
end
end
diff --git a/activerecord/test/cases/connection_management_test.rb b/activerecord/test/cases/connection_management_test.rb
index bab624b78a..dff6ea0fb0 100644
--- a/activerecord/test/cases/connection_management_test.rb
+++ b/activerecord/test/cases/connection_management_test.rb
@@ -26,29 +26,6 @@ module ActiveRecord
assert ActiveRecord::Base.connection_handler.active_connections?
end
- if Process.respond_to?(:fork)
- def test_connection_pool_per_pid
- object_id = ActiveRecord::Base.connection.object_id
-
- rd, wr = IO.pipe
- rd.binmode
- wr.binmode
-
- pid = fork {
- rd.close
- wr.write Marshal.dump ActiveRecord::Base.connection.object_id
- wr.close
- exit!
- }
-
- wr.close
-
- Process.waitpid pid
- assert_not_equal object_id, Marshal.load(rd.read)
- rd.close
- end
- end
-
def test_app_delegation
manager = ConnectionManagement.new(@app)
diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb
index f5928814a3..c905772193 100644
--- a/activerecord/test/cases/connection_pool_test.rb
+++ b/activerecord/test/cases/connection_pool_test.rb
@@ -100,7 +100,7 @@ module ActiveRecord
t = Thread.new { @pool.checkout }
# make sure our thread is in the timeout section
- Thread.pass until t.status == "sleep"
+ Thread.pass until @pool.num_waiting_in_queue == 1
connection = cs.first
connection.close
@@ -112,7 +112,7 @@ module ActiveRecord
t = Thread.new { @pool.checkout }
# make sure our thread is in the timeout section
- Thread.pass until t.status == "sleep"
+ Thread.pass until @pool.num_waiting_in_queue == 1
connection = cs.first
@pool.remove connection
@@ -234,7 +234,7 @@ module ActiveRecord
mutex.synchronize { errors << e }
end
}
- Thread.pass until t.status == "sleep"
+ Thread.pass until @pool.num_waiting_in_queue == i
t
end
@@ -271,7 +271,7 @@ module ActiveRecord
mutex.synchronize { errors << e }
end
}
- Thread.pass until t.status == "sleep"
+ Thread.pass until @pool.num_waiting_in_queue == i
t
end
@@ -356,6 +356,170 @@ module ActiveRecord
pool.checkin connection
end
+
+ def test_concurrent_connection_establishment
+ assert_operator @pool.connections.size, :<=, 1
+
+ all_threads_in_new_connection = ActiveSupport::Concurrency::Latch.new(@pool.size - @pool.connections.size)
+ all_go = ActiveSupport::Concurrency::Latch.new
+
+ @pool.singleton_class.class_eval do
+ define_method(:new_connection) do
+ all_threads_in_new_connection.release
+ all_go.await
+ super()
+ end
+ end
+
+ connecting_threads = []
+ @pool.size.times do
+ connecting_threads << Thread.new { @pool.checkout }
+ end
+
+ begin
+ Timeout.timeout(5) do
+ # the kernel of the whole test is here, everything else is just scaffolding,
+ # this latch will not be released unless conn. pool allows for concurrent
+ # connection creation
+ all_threads_in_new_connection.await
+ end
+ rescue Timeout::Error
+ flunk 'pool unable to establish connections concurrently or implementation has ' <<
+ 'changed, this test then needs to patch a different :new_connection method'
+ ensure
+ # clean up the threads
+ all_go.release
+ connecting_threads.map(&:join)
+ end
+ end
+
+ def test_non_bang_disconnect_and_clear_reloadable_connections_throw_exception_if_threads_dont_return_their_conns
+ @pool.checkout_timeout = 0.001 # no need to delay test suite by waiting the whole full default timeout
+ [:disconnect, :clear_reloadable_connections].each do |group_action_method|
+ @pool.with_connection do |connection|
+ assert_raises(ExclusiveConnectionTimeoutError) do
+ Thread.new { @pool.send(group_action_method) }.join
+ end
+ end
+ end
+ end
+
+ def test_disconnect_and_clear_reloadable_connections_attempt_to_wait_for_threads_to_return_their_conns
+ [:disconnect, :disconnect!, :clear_reloadable_connections, :clear_reloadable_connections!].each do |group_action_method|
+ begin
+ thread = timed_join_result = nil
+ @pool.with_connection do |connection|
+ thread = Thread.new { @pool.send(group_action_method) }
+
+ # give the other `thread` some time to get stuck in `group_action_method`
+ timed_join_result = thread.join(0.3)
+ # thread.join # => `nil` means the other thread hasn't finished running and is still waiting for us to
+ # release our connection
+ assert_nil timed_join_result
+
+ # assert that since this is within default timeout our connection hasn't been forcefully taken away from us
+ assert @pool.active_connection?
+ end
+ ensure
+ thread.join if thread && !timed_join_result # clean up the other thread
+ end
+ end
+ end
+
+ def test_bang_versions_of_disconnect_and_clear_reloadable_connections_if_unable_to_aquire_all_connections_proceed_anyway
+ @pool.checkout_timeout = 0.001 # no need to delay test suite by waiting the whole full default timeout
+ [:disconnect!, :clear_reloadable_connections!].each do |group_action_method|
+ @pool.with_connection do |connection|
+ Thread.new { @pool.send(group_action_method) }.join
+ # assert connection has been forcefully taken away from us
+ assert_not @pool.active_connection?
+ end
+ end
+ end
+
+ def test_disconnect_and_clear_reloadable_connections_are_able_to_preempt_other_waiting_threads
+ with_single_connection_pool do |pool|
+ [:disconnect, :disconnect!, :clear_reloadable_connections, :clear_reloadable_connections!].each do |group_action_method|
+ conn = pool.connection # drain the only available connection
+ second_thread_done = ActiveSupport::Concurrency::Latch.new
+
+ # create a first_thread and let it get into the FIFO queue first
+ first_thread = Thread.new do
+ pool.with_connection { second_thread_done.await }
+ end
+
+ # wait for first_thread to get in queue
+ Thread.pass until pool.num_waiting_in_queue == 1
+
+ # create a different, later thread, that will attempt to do a "group action",
+ # but because of the group action semantics it should be able to preempt the
+ # first_thread when a connection is made available
+ second_thread = Thread.new do
+ pool.send(group_action_method)
+ second_thread_done.release
+ end
+
+ # wait for second_thread to get in queue
+ Thread.pass until pool.num_waiting_in_queue == 2
+
+ # return the only available connection
+ pool.checkin(conn)
+
+ # if the second_thread is not able to preempt the first_thread,
+ # they will temporarily (until either of them timeouts with ConnectionTimeoutError)
+ # deadlock and a join(2) timeout will be reached
+ failed = true unless second_thread.join(2)
+
+ #--- post test clean up start
+ second_thread_done.release if failed
+
+ # after `pool.disconnect()` the first thread will be left stuck in queue, no need to wait for
+ # it to timeout with ConnectionTimeoutError
+ if (group_action_method == :disconnect || group_action_method == :disconnect!) && pool.num_waiting_in_queue > 0
+ pool.with_connection {} # create a new connection in case there are threads still stuck in a queue
+ end
+
+ first_thread.join
+ second_thread.join
+ #--- post test clean up end
+
+ flunk "#{group_action_method} is not able to preempt other waiting threads" if failed
+ end
+ end
+ end
+
+ def test_clear_reloadable_connections_creates_new_connections_for_waiting_threads_if_necessary
+ with_single_connection_pool do |pool|
+ conn = pool.connection # drain the only available connection
+ def conn.requires_reloading? # make sure it gets removed from the pool by clear_reloadable_connections
+ true
+ end
+
+ stuck_thread = Thread.new do
+ pool.with_connection {}
+ end
+
+ # wait for stuck_thread to get in queue
+ Thread.pass until pool.num_waiting_in_queue == 1
+
+ pool.clear_reloadable_connections
+
+ unless stuck_thread.join(2)
+ flunk 'clear_reloadable_connections must not let other connection waiting threads get stuck in queue'
+ end
+
+ assert_equal 0, pool.num_waiting_in_queue
+ end
+ end
+
+ private
+ def with_single_connection_pool
+ one_conn_spec = ActiveRecord::Base.connection_pool.spec.dup
+ one_conn_spec.config[:pool] = 1 # this is safe to do, because .dupped ConnectionSpecification also auto-dups its config
+ yield(pool = ConnectionPool.new(one_conn_spec))
+ ensure
+ pool.disconnect! if pool
+ end
end
end
end
diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb
index 83be9a75d8..b8433f0bba 100644
--- a/activerecord/test/cases/primary_keys_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -175,6 +175,14 @@ class PrimaryKeysTest < ActiveRecord::TestCase
dashboard = Dashboard.first
assert_equal '2', dashboard.id
end
+
+ if current_adapter?(:PostgreSQLAdapter)
+ def test_serial_with_quoted_sequence_name
+ column = MixedCaseMonkey.columns_hash[MixedCaseMonkey.primary_key]
+ assert_equal "nextval('\"mixed_case_monkeys_monkeyID_seq\"'::regclass)", column.default_function
+ assert column.serial?
+ end
+ end
end
class PrimaryKeyWithNoConnectionTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/type/integer_test.rb b/activerecord/test/cases/type/integer_test.rb
index 84fb05dd8e..0dcdbd0667 100644
--- a/activerecord/test/cases/type/integer_test.rb
+++ b/activerecord/test/cases/type/integer_test.rb
@@ -21,7 +21,7 @@ module ActiveRecord
type = Type::Integer.new
assert_nil type.cast([1,2])
assert_nil type.cast({1 => 2})
- assert_nil type.cast((1..2))
+ assert_nil type.cast(1..2)
end
test "casting ActiveRecord models" do