1
0
Fork 0
mirror of https://github.com/mperham/connection_pool synced 2023-03-27 23:22:21 -04:00
connection_pool/test/test_connection_pool.rb

554 lines
12 KiB
Ruby
Raw Normal View History

require_relative "helper"
2011-05-14 18:36:17 -04:00
class TestConnectionPool < Minitest::Test
2011-05-14 18:36:17 -04:00
class NetworkConnection
SLEEP_TIME = 0.1
2015-04-16 21:16:34 -04:00
2011-05-14 22:42:07 -04:00
def initialize
@x = 0
end
2011-05-14 18:36:17 -04:00
def do_something
2011-05-14 22:42:07 -04:00
@x += 1
2015-04-16 21:16:34 -04:00
sleep SLEEP_TIME
2011-05-14 22:42:07 -04:00
@x
end
2011-05-14 22:42:07 -04:00
def fast
@x += 1
2011-05-14 18:36:17 -04:00
end
2012-02-08 12:44:18 -05:00
def do_something_with_block
@x += yield
2015-04-16 21:16:34 -04:00
sleep SLEEP_TIME
2012-02-08 12:44:18 -05:00
@x
end
def respond_to?(method_id, *args)
method_id == :do_magic || super(method_id, *args)
end
2011-05-14 18:36:17 -04:00
end
2013-05-25 13:19:19 -04:00
class Recorder
def initialize
@calls = []
end
attr_reader :calls
def do_work(label)
@calls << label
end
end
2014-03-13 22:12:05 -04:00
def use_pool(pool, size)
Array.new(size) {
Thread.new do
pool.with { sleep }
end
}.each do |thread|
Thread.pass until thread.status == "sleep"
end
end
def kill_threads(threads)
threads.each do |thread|
thread.kill
2015-01-18 14:51:45 -05:00
thread.join
end
end
2011-05-14 18:36:17 -04:00
def test_basic_multithreaded_usage
2015-04-16 21:16:34 -04:00
pool_size = 5
pool = ConnectionPool.new(size: pool_size) { NetworkConnection.new }
2015-04-16 21:16:34 -04:00
start = Time.new
generations = 3
2014-03-13 22:12:05 -04:00
result = Array.new(pool_size * generations) {
2014-03-13 22:12:05 -04:00
Thread.new do
pool.with do |net|
2011-05-14 18:36:17 -04:00
net.do_something
end
end
}.map(&:value)
2015-04-16 21:16:34 -04:00
finish = Time.new
assert_equal((1..generations).cycle(pool_size).sort, result.sort)
2011-09-25 22:23:35 -04:00
2015-04-16 21:16:34 -04:00
assert_operator(finish - start, :>, generations * NetworkConnection::SLEEP_TIME)
2011-05-14 18:36:17 -04:00
end
def test_timeout
pool = ConnectionPool.new(timeout: 0, size: 1) { NetworkConnection.new }
thread = Thread.new {
2011-05-14 18:36:17 -04:00
pool.with do |net|
net.do_something
2014-02-14 19:32:35 -05:00
sleep 0.01
2011-05-14 18:36:17 -04:00
end
}
2014-02-14 19:32:35 -05:00
Thread.pass while thread.status == "run"
2014-02-14 19:32:35 -05:00
2011-05-14 18:36:17 -04:00
assert_raises Timeout::Error do
pool.with { |net| net.do_something }
2011-05-14 18:36:17 -04:00
end
2014-02-14 19:32:35 -05:00
thread.join
pool.with do |conn|
refute_nil conn
end
2011-05-14 18:36:17 -04:00
end
2014-02-05 20:25:58 -05:00
def test_with
pool = ConnectionPool.new(timeout: 0, size: 1) { Object.new }
pool.with do
Thread.new {
assert_raises Timeout::Error do
pool.checkout
end
}.join
end
assert Thread.new { pool.checkout }.join
end
def test_with_timeout
pool = ConnectionPool.new(timeout: 0, size: 1) { Object.new }
assert_raises Timeout::Error do
Timeout.timeout(0.01) do
pool.with do |obj|
2018-06-04 15:41:58 -04:00
assert_equal 0, pool.available
2015-04-10 15:46:44 -04:00
sleep 0.015
end
end
end
2018-06-04 15:41:58 -04:00
assert_equal 1, pool.available
2015-04-10 15:00:29 -04:00
end
def test_invalid_size
assert_raises ArgumentError, TypeError do
ConnectionPool.new(timeout: 0, size: nil) { Object.new }
end
assert_raises ArgumentError, TypeError do
ConnectionPool.new(timeout: 0, size: "") { Object.new }
end
end
2019-01-08 17:18:47 -05:00
def test_handle_interrupt_ensures_checkin
pool = ConnectionPool.new(timeout: 0, size: 1) { Object.new }
2015-04-10 15:00:29 -04:00
def pool.checkout(options)
sleep 0.015
2015-04-10 15:46:44 -04:00
super
2015-04-10 15:00:29 -04:00
end
2015-04-10 15:00:29 -04:00
did_something = false
action = lambda do
2015-04-10 15:00:29 -04:00
Timeout.timeout(0.01) do
pool.with do |obj|
did_something = true
2015-04-10 15:46:44 -04:00
# Timeout::Error will be triggered by any non-trivial Ruby code
# executed here since it couldn't be raised during checkout.
# It looks like setting the local variable above does not trigger
# the Timeout check in MRI 2.2.1.
obj.tap { obj.hash }
2015-04-10 15:00:29 -04:00
end
end
end
if RUBY_ENGINE == "ruby"
# These asserts rely on the Ruby implementation reaching `did_something =
# true` before the interrupt is detected by the thread. Interrupt
# detection timing is implementation-specific in practice, with JRuby,
# Rubinius, and TruffleRuby all having different interrupt timings to MRI.
# In fact they generally detect interrupts more quickly than MRI, so they
# may not reach `did_something = true` before detecting the interrupt.
assert_raises Timeout::Error, &action
assert did_something
else
action.call
end
2018-06-04 15:41:58 -04:00
assert_equal 1, pool.available
end
2015-04-09 23:21:51 -04:00
def test_explicit_return
pool = ConnectionPool.new(timeout: 0, size: 1) {
2015-04-09 23:21:51 -04:00
mock = Minitest::Mock.new
def mock.disconnect!
raise "should not disconnect upon explicit return"
end
mock
}
2015-04-09 23:21:51 -04:00
pool.with do |conn|
return true
end
end
2014-02-05 20:25:58 -05:00
def test_with_timeout_override
pool = ConnectionPool.new(timeout: 0, size: 1) { NetworkConnection.new }
2014-02-14 19:24:29 -05:00
t = Thread.new {
2013-09-23 23:28:50 -04:00
pool.with do |net|
net.do_something
2014-02-14 19:24:29 -05:00
sleep 0.01
2013-09-23 23:28:50 -04:00
end
}
2014-02-14 19:24:29 -05:00
Thread.pass while t.status == "run"
2014-02-14 19:24:29 -05:00
2013-09-23 23:28:50 -04:00
assert_raises Timeout::Error do
pool.with { |net| net.do_something }
2014-02-05 20:25:58 -05:00
end
2014-02-14 19:24:29 -05:00
pool.with(timeout: 2 * NetworkConnection::SLEEP_TIME) do |conn|
2013-09-23 23:28:50 -04:00
refute_nil conn
2014-02-05 20:25:58 -05:00
end
2013-09-23 23:28:50 -04:00
end
2011-05-14 18:36:17 -04:00
2014-02-07 16:38:07 -05:00
def test_checkin
pool = ConnectionPool.new(timeout: 0, size: 1) { NetworkConnection.new }
2014-02-07 16:38:07 -05:00
conn = pool.checkout
Thread.new {
assert_raises Timeout::Error do
pool.checkout
end
}.join
2014-02-07 16:38:07 -05:00
pool.checkin
2015-01-18 01:09:47 -05:00
assert_same conn, Thread.new { pool.checkout }.value
2014-02-07 16:38:07 -05:00
end
def test_returns_value
pool = ConnectionPool.new(timeout: 0, size: 1) { Object.new }
assert_equal 1, pool.with { |o| 1 }
end
def test_checkin_never_checkout
pool = ConnectionPool.new(timeout: 0, size: 1) { Object.new }
e = assert_raises(ConnectionPool::Error) { pool.checkin }
assert_equal "no connections are checked out", e.message
end
def test_checkin_no_current_checkout
pool = ConnectionPool.new(timeout: 0, size: 1) { Object.new }
pool.checkout
pool.checkin
assert_raises ConnectionPool::Error do
pool.checkin
end
end
def test_checkin_twice
pool = ConnectionPool.new(timeout: 0, size: 1) { Object.new }
pool.checkout
pool.checkout
pool.checkin
Thread.new {
assert_raises Timeout::Error do
pool.checkout
end
}.join
pool.checkin
assert Thread.new { pool.checkout }.join
end
2014-02-07 17:05:29 -05:00
def test_checkout
pool = ConnectionPool.new(size: 1) { NetworkConnection.new }
2014-02-07 17:05:29 -05:00
conn = pool.checkout
assert_kind_of NetworkConnection, conn
assert_same conn, pool.checkout
end
def test_checkout_multithread
pool = ConnectionPool.new(size: 2) { NetworkConnection.new }
conn = pool.checkout
t = Thread.new {
pool.checkout
}
refute_same conn, t.value
end
def test_checkout_timeout
pool = ConnectionPool.new(timeout: 0, size: 0) { Object.new }
assert_raises Timeout::Error do
pool.checkout
end
end
2013-09-24 00:47:01 -04:00
def test_checkout_timeout_override
pool = ConnectionPool.new(timeout: 0, size: 1) { NetworkConnection.new }
thread = Thread.new {
2013-09-24 00:47:01 -04:00
pool.with do |net|
net.do_something
sleep 0.01
2013-09-24 00:47:01 -04:00
end
}
Thread.pass while thread.status == "run"
2013-09-24 00:47:01 -04:00
assert_raises Timeout::Error do
2014-02-05 20:25:58 -05:00
pool.checkout
2013-09-24 00:47:01 -04:00
end
assert pool.checkout timeout: 2 * NetworkConnection::SLEEP_TIME
2013-09-24 00:47:01 -04:00
end
2014-02-05 20:25:58 -05:00
2011-05-14 18:36:17 -04:00
def test_passthru
pool = ConnectionPool.wrap(timeout: 2 * NetworkConnection::SLEEP_TIME, size: 1) { NetworkConnection.new }
2011-05-14 22:42:07 -04:00
assert_equal 1, pool.do_something
assert_equal 2, pool.do_something
2012-02-08 12:44:18 -05:00
assert_equal 5, pool.do_something_with_block { 3 }
assert_equal 6, pool.with { |net| net.fast }
end
def test_passthru_respond_to
pool = ConnectionPool.wrap(timeout: 2 * NetworkConnection::SLEEP_TIME, size: 1) { NetworkConnection.new }
assert pool.respond_to?(:with)
assert pool.respond_to?(:do_something)
assert pool.respond_to?(:do_magic)
refute pool.respond_to?(:do_lots_of_magic)
2011-05-14 22:42:07 -04:00
end
def test_return_value
pool = ConnectionPool.new(timeout: 2 * NetworkConnection::SLEEP_TIME, size: 1) { NetworkConnection.new }
result = pool.with { |net|
2011-05-14 22:42:07 -04:00
net.fast
}
2011-05-14 22:42:07 -04:00
assert_equal 1, result
2011-05-14 18:36:17 -04:00
end
def test_heavy_threading
pool = ConnectionPool.new(timeout: 0.5, size: 3) { NetworkConnection.new }
2014-02-14 19:19:59 -05:00
threads = Array.new(20) {
Thread.new do
pool.with do |net|
2014-02-14 19:19:59 -05:00
sleep 0.01
end
end
}
2014-02-14 19:19:59 -05:00
threads.map { |thread| thread.join }
end
def test_reuses_objects_when_pool_not_saturated
pool = ConnectionPool.new(size: 5) { NetworkConnection.new }
ids = 10.times.map {
pool.with { |c| c.object_id }
}
assert_equal 1, ids.uniq.size
end
def test_nested_checkout
recorder = Recorder.new
pool = ConnectionPool.new(size: 1) { recorder }
pool.with do |r_outer|
@other = Thread.new { |t|
pool.with do |r_other|
r_other.do_work("other")
end
}
pool.with do |r_inner|
r_inner.do_work("inner")
end
2014-02-14 19:35:24 -05:00
Thread.pass
r_outer.do_work("outer")
end
@other.join
assert_equal ["inner", "outer", "other"], recorder.calls
end
2013-05-25 13:19:19 -04:00
def test_shutdown_is_executed_for_all_connections
recorders = []
pool = ConnectionPool.new(size: 3) {
2013-05-25 13:19:19 -04:00
Recorder.new.tap { |r| recorders << r }
}
2013-05-25 13:19:19 -04:00
threads = use_pool pool, 3
2013-05-25 13:19:19 -04:00
pool.shutdown do |recorder|
recorder.do_work("shutdown")
end
kill_threads(threads)
2013-05-25 13:19:19 -04:00
assert_equal [["shutdown"]] * 3, recorders.map { |r| r.calls }
end
def test_raises_error_after_shutting_down
pool = ConnectionPool.new(size: 1) { true }
2013-05-25 13:19:19 -04:00
pool.shutdown {}
2013-05-25 13:19:19 -04:00
assert_raises ConnectionPool::PoolShuttingDownError do
pool.checkout
end
end
def test_runs_shutdown_block_asynchronously_if_connection_was_in_use
recorders = []
pool = ConnectionPool.new(size: 3) {
2013-05-25 13:19:19 -04:00
Recorder.new.tap { |r| recorders << r }
}
2013-05-25 13:19:19 -04:00
threads = use_pool pool, 2
2013-05-25 13:19:19 -04:00
pool.checkout
pool.shutdown do |recorder|
recorder.do_work("shutdown")
end
kill_threads(threads)
2015-01-18 01:09:47 -05:00
assert_equal [["shutdown"], ["shutdown"], []], recorders.map { |r| r.calls }
2013-05-25 13:19:19 -04:00
pool.checkin
assert_equal [["shutdown"], ["shutdown"], ["shutdown"]], recorders.map { |r| r.calls }
end
def test_raises_an_error_if_shutdown_is_called_without_a_block
pool = ConnectionPool.new(size: 1) {}
2013-05-25 13:19:19 -04:00
assert_raises ArgumentError do
pool.shutdown
end
end
def test_shutdown_is_executed_for_all_connections_in_wrapped_pool
recorders = []
wrapper = ConnectionPool::Wrapper.new(size: 3) {
Recorder.new.tap { |r| recorders << r }
}
threads = use_pool wrapper, 3
wrapper.pool_shutdown do |recorder|
recorder.do_work("shutdown")
end
kill_threads(threads)
assert_equal [["shutdown"]] * 3, recorders.map { |r| r.calls }
end
2014-02-14 18:45:57 -05:00
def test_wrapper_wrapped_pool
wrapper = ConnectionPool::Wrapper.new { NetworkConnection.new }
assert_equal ConnectionPool, wrapper.wrapped_pool.class
end
2014-02-14 18:47:59 -05:00
def test_wrapper_method_missing
wrapper = ConnectionPool::Wrapper.new { NetworkConnection.new }
assert_equal 1, wrapper.fast
end
2014-02-14 18:45:57 -05:00
def test_wrapper_respond_to_eh
wrapper = ConnectionPool::Wrapper.new { NetworkConnection.new }
assert_respond_to wrapper, :with
assert_respond_to wrapper, :fast
refute_respond_to wrapper, :"nonexistent method"
end
2014-02-14 18:52:35 -05:00
def test_wrapper_with
wrapper = ConnectionPool::Wrapper.new(timeout: 0, size: 1) { Object.new }
2014-02-14 18:52:35 -05:00
wrapper.with do
Thread.new {
assert_raises Timeout::Error do
wrapper.with { flunk "connection checked out :(" }
end
}.join
2014-02-14 18:52:35 -05:00
end
assert Thread.new { wrapper.with {} }.join
2014-02-14 18:52:35 -05:00
end
class ConnWithEval
def eval(arg)
"eval'ed #{arg}"
end
end
def test_wrapper_kernel_methods
wrapper = ConnectionPool::Wrapper.new(timeout: 0, size: 1) { ConnWithEval.new }
assert_equal "eval'ed 1", wrapper.eval(1)
end
2014-03-13 22:12:05 -04:00
2016-05-18 10:21:56 -04:00
def test_wrapper_with_connection_pool
recorder = Recorder.new
pool = ConnectionPool.new(size: 1) { recorder }
wrapper = ConnectionPool::Wrapper.new(pool: pool)
pool.with { |r| r.do_work("with") }
wrapper.do_work("wrapped")
2016-05-18 10:21:56 -04:00
assert_equal ["with", "wrapped"], recorder.calls
2016-05-18 10:21:56 -04:00
end
def test_stats_without_active_connection
pool = ConnectionPool.new(size: 2) { NetworkConnection.new }
assert_equal(2, pool.size)
assert_equal(2, pool.available)
end
def test_stats_with_active_connection
pool = ConnectionPool.new(size: 2) { NetworkConnection.new }
pool.with do
assert_equal(1, pool.available)
end
end
def test_stats_with_string_size
pool = ConnectionPool.new(size: "2") { NetworkConnection.new }
pool.with do
assert_equal(2, pool.size)
assert_equal(1, pool.available)
end
end
end