Skip to content

Commit

Permalink
Adds two tests for worker SIGTERM/respawn and phased-restart
Browse files Browse the repository at this point in the history
test_worker_spawn_external_term - sends SIGTERM to workers, checks respawn, etc

test_worker_phased_restart - checking worker handling during phased-restart
  • Loading branch information
MSP-Greg committed Aug 13, 2019
1 parent 28879c1 commit fd640e7
Showing 1 changed file with 146 additions and 0 deletions.
146 changes: 146 additions & 0 deletions test/test_integration.rb
Expand Up @@ -392,4 +392,150 @@ def test_no_zombie_children
# Should return nil if Puma has correctly cleaned up
assert_nil Process.waitpid(-1, Process::WNOHANG)
end

def test_worker_spawn_external_term
skip NO_FORK_MSG unless HAS_FORK

host = '127.0.0.1'
port = UniquePort.call

conf = Puma::Configuration.new do |c|
c.bind "tcp://#{host}:#{port}"
c.threads 1, 1
c.workers 2
c.worker_shutdown_timeout 2
c.app TestApps::SLEEP
end

l = Puma::Launcher.new conf, :events => @events

t = Thread.new do
Thread.current.abort_on_exception = true
l.run
end

wait_booted
start_time = Time.now.to_f
cluster = l.instance_variable_get(:@runner)

http0 = Net::HTTP.new host, port
http1 = Net::HTTP.new host, port

worker0 = Thread.new do
req0 = Net::HTTP::Get.new '/sleep35'
http0.start.request(req0) { |rep| body0 = rep.body }
end

worker1 = Thread.new do
req1 = Net::HTTP::Get.new '/sleep40'
http1.start.request(req1) { |rep| body1 = rep.body }
end

old_pids = cluster.instance_variable_get(:@workers).map(&:pid)

old_pids.each { |p| Process.kill :TERM, p }

sleep 28
new_pids = cluster.instance_variable_get(:@workers).map(&:pid)

# should be empty if all old workers removed
old_waited = old_pids.map { |pid|
begin
Process.wait(pid, Process::WNOHANG)
pid
rescue Errno::ECHILD
nil # child is already terminated
end
}.compact

Thread.kill worker0
Thread.kill worker1
http0 = nil
http1 = nil
cluster = nil
l.stop
assert_kind_of Thread, t.join, "server didn't stop"

assert_operator (Time.now.to_f - start_time).round(2), :<, 32

msg = "old_pids #{old_pids.inspect} new_pids #{new_pids.inspect} old_waited #{old_waited.inspect}"
assert_equal 2, new_pids.length, msg
assert_equal 2, old_pids.length, msg
assert_equal new_pids, (new_pids - old_pids), "#{msg}\nBoth workers should be replaced"
assert_empty old_waited, msg
end

def test_worker_phased_restart
skip NO_FORK_MSG unless HAS_FORK

host = '127.0.0.1'
port = UniquePort.call

conf = Puma::Configuration.new do |c|
c.bind "tcp://#{host}:#{port}"
c.threads 1, 1
c.workers 2
c.worker_shutdown_timeout 2
c.app TestApps::SLEEP
end

l = Puma::Launcher.new conf, :events => @events

t = Thread.new do
Thread.current.abort_on_exception = true
l.run
end

wait_booted
start_time = Time.now.to_f
cluster = l.instance_variable_get :@runner

http0 = Net::HTTP.new host, port
http1 = Net::HTTP.new host, port

worker0 = Thread.new do
req0 = Net::HTTP::Get.new "/sleep35", {}
http0.start.request(req0) { |rep| body0 = rep.body }
end

worker1 = Thread.new do
req1 = Net::HTTP::Get.new "/sleep40", {}
http1.start.request(req1) { |rep| body1 = rep.body }
end

old_pids = cluster.instance_variable_get(:@workers).map(&:pid)

old_pids.each { |p| Process.kill :TERM, p }

l.phased_restart

sleep 28
new_pids = cluster.instance_variable_get(:@workers).map(&:pid)

# should be empty if all old workers removed
old_waited = old_pids.map { |pid|
begin
Process.wait(pid, Process::WNOHANG)
pid
rescue Errno::ECHILD
nil # child is already terminated
end
}.compact

Thread.kill worker0
Thread.kill worker1
http0 = nil
http1 = nil
cluster = nil
l.stop
assert_kind_of Thread, t.join, "server didn't stop"

assert_operator (Time.now.to_f - start_time).round(2), :<, 32

msg = "old_pids #{old_pids.inspect} new_pids #{new_pids.inspect} old_waited #{old_waited.inspect}"
assert_equal 2, new_pids.length, msg
assert_equal 2, old_pids.length, msg
assert_equal new_pids, (new_pids - old_pids), "#{msg}\nBoth workers should be replaced"
assert_empty old_waited, msg
end
end

0 comments on commit fd640e7

Please sign in to comment.