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

Add pumactl command to print thread backtraces #2054

Merged
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
2 changes: 1 addition & 1 deletion History.md
@@ -1,7 +1,7 @@
## Master

* Features
* Your feature goes here (#Github Number)
* Add pumactl `thread-backtraces` command to print thread backtraces (#2053)

* Bugfixes
* Your bugfix goes here (#Github Number)
Expand Down
8 changes: 8 additions & 0 deletions lib/puma/app/status.rb
Expand Up @@ -55,6 +55,14 @@ def call(env)

when /\/stats$/
rack_response(200, @cli.stats)

when /\/thread-backtraces$/
backtraces = []
@cli.thread_status do |name, backtrace|
backtraces << { name: name, backtrace: backtrace }
end

rack_response(200, backtraces.to_json)
else
rack_response 404, "Unsupported action", 'text/plain'
end
Expand Down
5 changes: 3 additions & 2 deletions lib/puma/control_cli.rb
Expand Up @@ -11,7 +11,8 @@
module Puma
class ControlCLI

COMMANDS = %w{halt restart phased-restart start stats status stop reload-worker-directory gc gc-stats}
COMMANDS = %w{halt restart phased-restart start stats status stop reload-worker-directory gc gc-stats thread-backtraces}
PRINTABLE_COMMANDS = %w{gc-stats stats thread-backtraces}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice.


def initialize(argv, stdout=STDOUT, stderr=STDERR)
@state = nil
Expand Down Expand Up @@ -187,7 +188,7 @@ def send_request
end

message "Command #{@command} sent success"
message response.last if @command == "stats" || @command == "gc-stats"
message response.last if PRINTABLE_COMMANDS.include?(@command)
end
ensure
server.close if server && !server.closed?
Expand Down
31 changes: 15 additions & 16 deletions lib/puma/launcher.rb
Expand Up @@ -205,6 +205,17 @@ def close_binder_listeners
@binder.close_listeners
end

def thread_status
Thread.list.each do |thread|
name = "Thread: TID-#{thread.object_id.to_s(36)}"
name += " #{thread['label']}" if thread['label']
name += " #{thread.name}" if thread.respond_to?(:name) && thread.name
backtrace = thread.backtrace || ["<no backtrace available>"]

yield name, backtrace
end
end

private

# If configured, write the pid of the current process out
Expand Down Expand Up @@ -323,21 +334,6 @@ def graceful_stop
log "- Goodbye!"
end

def log_thread_status
Thread.list.each do |thread|
log "Thread TID-#{thread.object_id.to_s(36)} #{thread['label']}"
logstr = "Thread: TID-#{thread.object_id.to_s(36)}"
logstr += " #{thread.name}" if thread.respond_to?(:name)
log logstr

if thread.backtrace
log thread.backtrace.join("\n")
else
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 @@ -457,7 +453,10 @@ def setup_signals

begin
Signal.trap "SIGINFO" do
log_thread_status
thread_status do |name, backtrace|
@events.log name
@events.log backtrace
end
end
rescue Exception
# Not going to log this one, as SIGINFO is *BSD only and would be pretty annoying
Expand Down
24 changes: 24 additions & 0 deletions test/test_cli.rb
Expand Up @@ -203,6 +203,30 @@ def test_control_stop
t.join if UNIX_SKT_EXIST
end

def test_control_thread_backtraces
skip UNIX_SKT_MSG unless UNIX_SKT_EXIST
url = "unix://#{@tmp_path}"

cli = Puma::CLI.new ["-b", "unix://#{@tmp_path2}",
"--control-url", url,
"--control-token", "",
"test/rackup/lobster.ru"], @events

t = Thread.new { cli.run }

wait_booted

s = UNIXSocket.new @tmp_path
s << "GET /thread-backtraces HTTP/1.0\r\n\r\n"
body = s.read
s.close

assert_match %r{Thread: TID-}, body.split("\r\n").last
ensure
cli.launcher.stop if cli
t.join if UNIX_SKT_EXIST
end

def control_gc_stats(uri, cntl)
cli = Puma::CLI.new ["-b", uri,
"--control-url", cntl,
Expand Down
2 changes: 1 addition & 1 deletion test/test_integration_cluster.rb
Expand Up @@ -44,7 +44,7 @@ def test_siginfo_thread_print
Process.kill :INT , @pid
t.join

assert_match "Thread TID", output.join
assert_match "Thread: TID", output.join
end

def test_usr2_restart
Expand Down
2 changes: 1 addition & 1 deletion test/test_integration_single.rb
Expand Up @@ -99,6 +99,6 @@ def test_siginfo_thread_print
Process.kill :INT , @pid
t.join

assert_match "Thread TID", output.join
assert_match "Thread: TID", output.join
end
end
7 changes: 3 additions & 4 deletions test/test_launcher.rb
Expand Up @@ -57,10 +57,9 @@ def test_puma_wild_location_is_an_absolute_path
end

def test_prints_thread_traces
launcher.send(:log_thread_status)
events.stdout.rewind

assert_match "Thread TID", events.stdout.read
launcher.thread_status do |name, _backtrace|
assert_match "Thread: TID", name
end
end

def test_pid_file
Expand Down