Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SIGINFO prints thread backtraces #1320

Merged
merged 1 commit into from Sep 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions lib/puma/cluster.rb
Expand Up @@ -346,8 +346,8 @@ def reload_worker_directory
log "+ Changing to #{dir}"
Dir.chdir dir
end
# Inside of a child process, this will return all zeroes, as @workers is only populated in

# Inside of a child process, this will return all zeroes, as @workers is only populated in
# the master process.
def stats
old_worker_count = @workers.count { |w| w.phase != @phase }
Expand Down
20 changes: 20 additions & 0 deletions lib/puma/launcher.rb
Expand Up @@ -325,6 +325,17 @@ def graceful_stop
log "- Goodbye!"
end

def log_thread_status
Thread.list.each do |thread|
@events.log "Thread TID-#{thread.object_id.to_s(36)} #{thread['label']}"
if thread.backtrace
@events.log thread.backtrace.join("\n")
else
@events.log "<no backtrace available>"
end
end
end

def set_process_title
Process.respond_to?(:setproctitle) ? Process.setproctitle(title) : $0 = title
end
Expand Down Expand Up @@ -441,6 +452,15 @@ def setup_signals
rescue Exception
log "*** SIGHUP not implemented, signal based logs reopening unavailable!"
end

begin
Signal.trap "SIGINFO" do
log_thread_status
end
rescue Exception
# Not going to log this one, as SIGINFO is *BSD only and would be pretty annoying
# to see this constantly on Linux.
end
end

def require_rubygems_min_version!(min_version, feature)
Expand Down
2 changes: 1 addition & 1 deletion test/helpers/integration.rb
Expand Up @@ -97,7 +97,7 @@ def read_body(connection)
end

# gets worker pids from @server output
def get_worker_pids(phase, size = WORKERS)
def get_worker_pids(phase = 0, size = WORKERS)
pids = []
re = /pid: (\d+)\) booted, phase: #{phase}/
while pids.size < size
Expand Down
16 changes: 15 additions & 1 deletion test/test_integration_cluster.rb
Expand Up @@ -8,6 +8,20 @@ def setup
super
end

def test_siginfo_thread_print
skip_unless_signal_exist? :INFO

cli_server("-w #{WORKERS} -q test/rackup/hello.ru")
worker_pids = get_worker_pids
output = []
t = Thread.new { output << @server.readlines }
Process.kill(:INFO, worker_pids.first)
Process.kill(:INT, @server.pid)
t.join

assert_match "Thread TID", output.join
end

def test_usr2_restart
_, new_reply = restart_server_and_listen("-q -w #{WORKERS} test/rackup/hello.ru")
assert_equal "Hello World", new_reply
Expand Down Expand Up @@ -92,7 +106,7 @@ def test_term_worker_clean_exit
pid = cli_server("-w #{WORKERS} test/rackup/hello.ru").pid

# Get the PIDs of the child workers.
worker_pids = get_worker_pids 0
worker_pids = get_worker_pids

# Signal the workers to terminate, and wait for them to die.
Process.kill :TERM, pid
Expand Down
13 changes: 13 additions & 0 deletions test/test_integration_single.rb
Expand Up @@ -84,4 +84,17 @@ def test_int_signal_with_background_thread_in_jruby

assert_raises(Errno::ECONNREFUSED) { TCPSocket.new(HOST, @tcp_port) }
end

def test_siginfo_thread_print
skip_unless_signal_exist? :INFO

cli_server("test/rackup/hello.ru")
output = []
t = Thread.new { output << @server.readlines }
Process.kill(:INFO, @server.pid)
Process.kill(:INT, @server.pid)
t.join

assert_match "Thread TID", output.join
end
end
10 changes: 10 additions & 0 deletions test/test_launcher.rb
Expand Up @@ -60,4 +60,14 @@ def test_puma_wild_location_is_an_absolute_path
# assert no "/../" in path
refute_match(%r{/\.\./}, puma_wild_location)
end

def test_prints_thread_traces
events = Puma::Events.strings
l = Puma::Launcher.new(Puma::Configuration.new, events: events)

l.send(:log_thread_status)
events.stdout.rewind

assert_match "Thread TID", events.stdout.read
end
end
7 changes: 1 addition & 6 deletions test/test_puma_server.rb
Expand Up @@ -9,7 +9,7 @@ def setup

@app = lambda { |env| [200, {}, [env['rack.url_scheme']]] }

@events = Puma::Events.new STDOUT, STDERR
@events = Puma::Events.strings
@server = Puma::Server.new @app, @events
end

Expand Down Expand Up @@ -280,9 +280,6 @@ def test_GET_with_no_body_has_sane_chunking
end

def test_doesnt_print_backtrace_in_production
@events = Puma::Events.strings
@server = Puma::Server.new @app, @events

@server.app = proc { |e| raise "don't leak me bro" }
@server.leak_stack_on_error = false
@server.add_tcp_listener @host, @port
Expand All @@ -298,7 +295,6 @@ def test_doesnt_print_backtrace_in_production
end

def test_prints_custom_error
@events = Puma::Events.strings
re = lambda { |err| [302, {'Content-Type' => 'text', 'Location' => 'foo.html'}, ['302 found']] }
@server = Puma::Server.new @app, @events, {:lowlevel_error_handler => re}

Expand All @@ -314,7 +310,6 @@ def test_prints_custom_error
end

def test_leh_gets_env_as_well
@events = Puma::Events.strings
re = lambda { |err,env|
env['REQUEST_PATH'] || raise("where is env?")
[302, {'Content-Type' => 'text', 'Location' => 'foo.html'}, ['302 found']]
Expand Down