2017-05-12 15:16:55 -04:00
|
|
|
require_relative "helper"
|
2011-09-20 01:16:31 -04:00
|
|
|
|
2016-11-22 10:05:49 -05:00
|
|
|
require "puma/thread_pool"
|
2011-09-20 01:16:31 -04:00
|
|
|
|
2016-11-22 10:05:49 -05:00
|
|
|
class TestThreadPool < Minitest::Test
|
2011-09-20 01:16:31 -04:00
|
|
|
|
|
|
|
def teardown
|
2019-07-27 12:47:19 -04:00
|
|
|
@pool.shutdown(1) if @pool
|
2011-09-20 01:16:31 -04:00
|
|
|
end
|
|
|
|
|
2013-07-03 10:25:48 -04:00
|
|
|
def new_pool(min, max, &block)
|
|
|
|
block = proc { } unless block
|
|
|
|
@pool = Puma::ThreadPool.new(min, max, &block)
|
2011-09-20 01:16:31 -04:00
|
|
|
end
|
|
|
|
|
2020-04-14 19:43:59 -04:00
|
|
|
def mutex_pool(min, max, &block)
|
|
|
|
block = proc { } unless block
|
|
|
|
@pool = MutexPool.new(min, max, &block)
|
|
|
|
end
|
|
|
|
|
|
|
|
# Wraps ThreadPool work in mutex for better concurrency control.
|
|
|
|
class MutexPool < Puma::ThreadPool
|
|
|
|
# Wait until the added work is completed before returning.
|
|
|
|
# Array argument is treated as a batch of work items to be added.
|
|
|
|
# Block will run after work is added but before it is executed on a worker thread.
|
|
|
|
def <<(work, &block)
|
|
|
|
work = [work] unless work.is_a?(Array)
|
|
|
|
with_mutex do
|
|
|
|
work.each {|arg| super arg}
|
|
|
|
yield if block_given?
|
|
|
|
@not_full.wait(@mutex)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def signal
|
|
|
|
@not_full.signal
|
|
|
|
end
|
|
|
|
|
|
|
|
# If +wait+ is true, wait until the trim request is completed before returning.
|
|
|
|
def trim(force=false, wait: true)
|
|
|
|
super(force)
|
|
|
|
Thread.pass until @trim_requested == 0 if wait
|
|
|
|
end
|
2011-09-23 23:49:45 -04:00
|
|
|
end
|
|
|
|
|
2011-09-20 01:16:31 -04:00
|
|
|
def test_append_spawns
|
|
|
|
saw = []
|
2020-04-14 19:43:59 -04:00
|
|
|
pool = mutex_pool(0, 1) do |work|
|
|
|
|
saw << work
|
2011-09-20 01:16:31 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
pool << 1
|
2020-04-14 19:43:59 -04:00
|
|
|
assert_equal 1, pool.spawned
|
|
|
|
assert_equal [1], saw
|
|
|
|
end
|
2011-09-20 01:16:31 -04:00
|
|
|
|
2020-04-14 19:43:59 -04:00
|
|
|
def test_thread_name
|
|
|
|
skip 'Thread.name not supported' unless Thread.current.respond_to?(:name)
|
|
|
|
thread_name = nil
|
|
|
|
pool = mutex_pool(0, 1) {thread_name = Thread.current.name}
|
|
|
|
pool << 1
|
|
|
|
assert_equal('puma threadpool 001', thread_name)
|
2011-09-20 01:16:31 -04:00
|
|
|
end
|
|
|
|
|
2013-07-16 20:07:59 -04:00
|
|
|
def test_converts_pool_sizes
|
|
|
|
pool = new_pool('0', '1')
|
|
|
|
|
|
|
|
assert_equal 0, pool.spawned
|
|
|
|
|
|
|
|
pool << 1
|
|
|
|
|
|
|
|
assert_equal 1, pool.spawned
|
|
|
|
end
|
|
|
|
|
2011-09-20 01:16:31 -04:00
|
|
|
def test_append_queues_on_max
|
2019-07-27 12:47:19 -04:00
|
|
|
pool = new_pool(0, 0) do
|
|
|
|
"Hello World!"
|
|
|
|
end
|
2011-09-20 01:16:31 -04:00
|
|
|
|
|
|
|
pool << 1
|
|
|
|
pool << 2
|
|
|
|
pool << 3
|
|
|
|
|
2019-07-27 12:47:19 -04:00
|
|
|
assert_equal 3, pool.backlog
|
2011-09-20 01:16:31 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def test_trim
|
2020-04-14 19:43:59 -04:00
|
|
|
pool = mutex_pool(0, 1)
|
2011-09-20 01:16:31 -04:00
|
|
|
|
|
|
|
pool << 1
|
|
|
|
|
2020-04-14 19:43:59 -04:00
|
|
|
assert_equal 1, pool.spawned
|
2011-09-23 23:49:45 -04:00
|
|
|
|
2011-09-20 01:16:31 -04:00
|
|
|
pool.trim
|
|
|
|
assert_equal 0, pool.spawned
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_trim_leaves_min
|
2020-04-14 19:43:59 -04:00
|
|
|
pool = mutex_pool(1, 2)
|
2011-09-20 01:16:31 -04:00
|
|
|
|
2020-04-14 19:43:59 -04:00
|
|
|
pool << [1, 2]
|
2011-09-20 01:16:31 -04:00
|
|
|
|
2020-04-14 19:43:59 -04:00
|
|
|
assert_equal 2, pool.spawned
|
2012-02-07 12:49:23 -05:00
|
|
|
|
2011-09-20 01:16:31 -04:00
|
|
|
pool.trim
|
|
|
|
assert_equal 1, pool.spawned
|
2019-07-27 12:47:19 -04:00
|
|
|
|
2011-09-20 01:16:31 -04:00
|
|
|
pool.trim
|
|
|
|
assert_equal 1, pool.spawned
|
|
|
|
end
|
|
|
|
|
2012-02-07 12:49:23 -05:00
|
|
|
def test_force_trim_doesnt_overtrim
|
2020-04-14 19:43:59 -04:00
|
|
|
pool = mutex_pool(1, 2)
|
2012-02-07 12:49:23 -05:00
|
|
|
|
2020-04-14 19:43:59 -04:00
|
|
|
pool.<< [1, 2] do
|
|
|
|
assert_equal 2, pool.spawned
|
|
|
|
pool.trim true, wait: false
|
|
|
|
pool.trim true, wait: false
|
|
|
|
end
|
2012-02-07 12:49:23 -05:00
|
|
|
|
|
|
|
assert_equal 1, pool.spawned
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_trim_is_ignored_if_no_waiting_threads
|
2020-04-14 19:43:59 -04:00
|
|
|
pool = mutex_pool(1, 2)
|
2011-09-20 01:16:31 -04:00
|
|
|
|
2020-04-14 19:43:59 -04:00
|
|
|
pool.<< [1, 2] do
|
|
|
|
assert_equal 2, pool.spawned
|
|
|
|
pool.trim
|
|
|
|
pool.trim
|
|
|
|
end
|
2011-09-20 01:16:31 -04:00
|
|
|
|
|
|
|
assert_equal 2, pool.spawned
|
2012-07-30 19:37:43 -04:00
|
|
|
assert_equal 0, pool.trim_requested
|
2011-09-20 01:16:31 -04:00
|
|
|
end
|
2011-12-05 13:07:01 -05:00
|
|
|
|
|
|
|
def test_autotrim
|
2020-04-14 19:43:59 -04:00
|
|
|
pool = mutex_pool(1, 2)
|
2011-12-05 13:07:01 -05:00
|
|
|
|
2020-04-14 19:43:59 -04:00
|
|
|
timeout = 0
|
|
|
|
pool.auto_trim! timeout
|
2011-12-05 13:07:01 -05:00
|
|
|
|
2020-04-14 19:43:59 -04:00
|
|
|
pool.<< [1, 2] do
|
|
|
|
assert_equal 2, pool.spawned
|
|
|
|
end
|
2011-12-05 13:07:01 -05:00
|
|
|
|
2020-04-14 19:43:59 -04:00
|
|
|
start = Time.now
|
|
|
|
Thread.pass until pool.spawned == 1 ||
|
|
|
|
Time.now - start > 1
|
2011-12-05 13:07:01 -05:00
|
|
|
|
|
|
|
assert_equal 1, pool.spawned
|
|
|
|
end
|
2014-10-30 20:52:37 -04:00
|
|
|
|
|
|
|
def test_cleanliness
|
|
|
|
values = []
|
|
|
|
n = 100
|
|
|
|
|
2020-04-14 19:43:59 -04:00
|
|
|
pool = mutex_pool(1,1) {
|
|
|
|
values.push Thread.current[:foo]
|
2014-10-30 20:52:37 -04:00
|
|
|
Thread.current[:foo] = :hai
|
|
|
|
}
|
|
|
|
|
2014-11-27 01:50:45 -05:00
|
|
|
pool.clean_thread_locals = true
|
|
|
|
|
2020-04-14 19:43:59 -04:00
|
|
|
pool << [1] * n
|
2014-10-30 20:52:37 -04:00
|
|
|
|
|
|
|
assert_equal n, values.length
|
|
|
|
|
|
|
|
assert_equal [], values.compact
|
|
|
|
end
|
2015-05-19 10:14:30 -04:00
|
|
|
|
|
|
|
def test_reap_only_dead_threads
|
2020-04-14 19:43:59 -04:00
|
|
|
pool = mutex_pool(2,2) do
|
|
|
|
th = Thread.current
|
|
|
|
Thread.new {th.join; pool.signal}
|
|
|
|
th.kill
|
|
|
|
end
|
2015-05-19 10:14:30 -04:00
|
|
|
|
|
|
|
assert_equal 2, pool.spawned
|
|
|
|
|
|
|
|
pool << 1
|
|
|
|
|
|
|
|
assert_equal 2, pool.spawned
|
|
|
|
|
|
|
|
pool.reap
|
|
|
|
|
|
|
|
assert_equal 1, pool.spawned
|
|
|
|
|
|
|
|
pool << 2
|
|
|
|
|
|
|
|
assert_equal 1, pool.spawned
|
|
|
|
|
|
|
|
pool.reap
|
|
|
|
|
|
|
|
assert_equal 0, pool.spawned
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_auto_reap_dead_threads
|
2020-04-14 19:43:59 -04:00
|
|
|
pool = mutex_pool(2,2) do
|
|
|
|
th = Thread.current
|
|
|
|
Thread.new {th.join; pool.signal}
|
|
|
|
th.kill
|
|
|
|
end
|
2019-10-16 21:17:19 -04:00
|
|
|
|
2020-04-14 19:43:59 -04:00
|
|
|
timeout = 0
|
|
|
|
pool.auto_reap! timeout
|
2019-10-16 21:17:19 -04:00
|
|
|
|
2015-05-19 10:14:30 -04:00
|
|
|
assert_equal 2, pool.spawned
|
|
|
|
|
|
|
|
pool << 1
|
|
|
|
pool << 2
|
|
|
|
|
2020-04-14 19:43:59 -04:00
|
|
|
start = Time.now
|
|
|
|
Thread.pass until pool.spawned == 0 ||
|
|
|
|
Time.now - start > 1
|
2015-05-19 10:14:30 -04:00
|
|
|
|
|
|
|
assert_equal 0, pool.spawned
|
|
|
|
end
|
2016-11-20 13:36:47 -05:00
|
|
|
|
|
|
|
def test_force_shutdown_immediately
|
2019-10-16 21:17:19 -04:00
|
|
|
rescued = false
|
|
|
|
|
2020-04-14 19:43:59 -04:00
|
|
|
pool = mutex_pool(0, 1) do
|
2016-11-20 13:36:47 -05:00
|
|
|
begin
|
2020-04-14 19:43:59 -04:00
|
|
|
pool.signal
|
|
|
|
sleep
|
2016-11-20 13:36:47 -05:00
|
|
|
rescue Puma::ThreadPool::ForceShutdown
|
2019-10-16 21:17:19 -04:00
|
|
|
rescued = true
|
2016-11-20 13:36:47 -05:00
|
|
|
end
|
2019-07-27 12:47:19 -04:00
|
|
|
end
|
2016-11-20 13:36:47 -05:00
|
|
|
|
|
|
|
pool << 1
|
2020-04-14 19:43:59 -04:00
|
|
|
pool.shutdown(0)
|
2016-11-20 13:36:47 -05:00
|
|
|
|
2020-04-14 19:43:59 -04:00
|
|
|
assert_equal 0, pool.spawned
|
|
|
|
assert rescued
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_waiting_on_startup
|
|
|
|
pool = new_pool(1, 2)
|
|
|
|
assert_equal 1, pool.waiting
|
2016-11-20 13:36:47 -05:00
|
|
|
end
|
2020-04-16 21:38:30 -04:00
|
|
|
|
|
|
|
def test_shutdown_with_grace
|
|
|
|
timeout = 0.01
|
|
|
|
grace = 0.01
|
|
|
|
|
|
|
|
rescued = []
|
|
|
|
pool = mutex_pool(2, 2) do
|
|
|
|
begin
|
|
|
|
pool.signal
|
|
|
|
sleep
|
|
|
|
rescue Puma::ThreadPool::ForceShutdown
|
|
|
|
rescued << Thread.current
|
|
|
|
sleep
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
pool << 1
|
|
|
|
pool << 2
|
|
|
|
|
|
|
|
Puma::ThreadPool.stub_const(:SHUTDOWN_GRACE_TIME, grace) do
|
|
|
|
pool.shutdown(timeout)
|
|
|
|
end
|
|
|
|
assert_equal 0, pool.spawned
|
|
|
|
assert_equal 2, rescued.length
|
2020-05-15 17:49:35 -04:00
|
|
|
refute rescued.compact.any?(&:alive?)
|
2020-04-16 21:38:30 -04:00
|
|
|
end
|
2017-09-01 15:31:47 -04:00
|
|
|
|
|
|
|
def test_correct_waiting_count_for_killed_threads
|
|
|
|
pool = new_pool(1, 1) { |_| }
|
2020-06-10 19:25:24 -04:00
|
|
|
sleep 1
|
2017-09-01 15:31:47 -04:00
|
|
|
|
|
|
|
# simulate our waiting worker thread getting killed for whatever reason
|
|
|
|
pool.instance_eval { @workers[0].kill }
|
2020-06-10 19:25:24 -04:00
|
|
|
sleep 1
|
2017-09-01 15:31:47 -04:00
|
|
|
pool.reap
|
2020-06-10 19:25:24 -04:00
|
|
|
sleep 1
|
2017-09-01 15:31:47 -04:00
|
|
|
|
|
|
|
pool << 0
|
2020-06-10 19:25:24 -04:00
|
|
|
sleep 1
|
2017-09-01 15:31:47 -04:00
|
|
|
assert_equal 0, pool.backlog
|
|
|
|
end
|
2011-09-20 01:16:31 -04:00
|
|
|
end
|