Skip to content

Commit

Permalink
Use SIGPROF to print thread backtraces
Browse files Browse the repository at this point in the history
puma#1320 added support for using SIGINFO,
but this is only available on BSD-based systems. SIGPROF is on Linux.

This is useful for debugging infinite loops or slow performance.
  • Loading branch information
stanhu committed Mar 22, 2020
1 parent c87f088 commit 6a12ca8
Show file tree
Hide file tree
Showing 4 changed files with 35 additions and 13 deletions.
1 change: 1 addition & 0 deletions History.md
Expand Up @@ -7,6 +7,7 @@
* `GC.compact` is called before fork if available (#2093)
* Add `requests_count` to workers stats. (#2106)
* Increases maximum URI path length from 2048 to 8196 bytes (#2167)
* Sending SIGPROF to any Puma worker now prints currently active threads and their backtraces (#2195)

* Deprecations, Removals and Breaking API Changes
* `Puma.stats` now returns a Hash instead of a JSON string (#2086)
Expand Down
2 changes: 1 addition & 1 deletion docs/signals.md
Expand Up @@ -40,7 +40,7 @@ Puma cluster responds to these signals:
- `USR1` restart workers in phases, a rolling restart. This will not reload configuration file.
- `HUP` reopen log files defined in stdout_redirect configuration parameter. If there is no stdout_redirect option provided it will behave like `INT`
- `INT` equivalent of sending Ctrl-C to cluster. Will attempt to finish then exit.
- `CHLD`
- `PROF` (`INFO` on BSD-based systems) prints a thread backtrace. This is useful for debugging infinite loops or slow performance.

## Callbacks order in case of different signals

Expand Down
19 changes: 15 additions & 4 deletions lib/puma/launcher.rb
Expand Up @@ -456,19 +456,30 @@ def setup_signals
log "*** SIGHUP not implemented, signal based logs reopening unavailable!"
end

begin
Signal.trap "SIGPROF" do
log_backtrace
end
rescue Exception
end

begin
Signal.trap "SIGINFO" do
thread_status do |name, backtrace|
@events.log name
@events.log backtrace.map { |bt| " #{bt}" }
end
log_backtrace
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 log_backtrace
thread_status do |name, backtrace|
@events.log name
@events.log backtrace.map { |bt| " #{bt}" }
end
end

def require_rubygems_min_version!(min_version, feature)
return if min_version <= Gem::Version.new(Gem::VERSION)

Expand Down
26 changes: 18 additions & 8 deletions test/test_integration_cluster.rb
Expand Up @@ -36,15 +36,13 @@ def test_pre_existing_unix
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 , @pid
t.join
signal_thread_backtrace :INFO
end

assert_match "Thread: TID", output.join
def test_sigprof_thread_print
skip_unless_signal_exist? :PROF

signal_thread_backtrace :PROF
end

def test_usr2_restart
Expand Down Expand Up @@ -135,6 +133,18 @@ def test_stuck_phased_restart

private

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

assert_match "Thread: TID", output.join
end

# Send requests 10 per second. Send 10, then :TERM server, then send another 30.
# No more than 10 should throw Errno::ECONNRESET.
def term_closes_listeners(unix: false)
Expand Down

0 comments on commit 6a12ca8

Please sign in to comment.