Skip to content

Commit

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

This is useful for debugging infinite loops or slow performance.
  • Loading branch information
stanhu committed Mar 24, 2020
1 parent 43d1c06 commit 3bf45fb
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 14 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 SIGWINCH 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`
- `WINCH` (`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
20 changes: 16 additions & 4 deletions lib/puma/launcher.rb
Expand Up @@ -452,13 +452,18 @@ def setup_signals
log "*** SIGHUP not implemented, signal based logs reopening unavailable!"
end

begin
Signal.trap "SIGWINCH" do
log_backtrace
end
rescue Exception
log "*** SIGWINCH not implemented, signal based thread backtraces unavailable!"
end

begin
unless Puma.jruby? # INFO in use by JVM already
Signal.trap "SIGINFO" do
thread_status do |name, backtrace|
@events.log name
@events.log backtrace.map { |bt| " #{bt}" }
end
log_backtrace
end
end
rescue Exception
Expand All @@ -467,6 +472,13 @@ def setup_signals
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
28 changes: 19 additions & 9 deletions test/test_integration_cluster.rb
Expand Up @@ -33,18 +33,16 @@ def test_pre_existing_unix
end
end

def test_sigwinch_thread_print
skip_unless_signal_exist? :WINCH

signal_thread_backtrace :WINCH
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 , @pid
t.join

assert_match "Thread: TID", output.join
signal_thread_backtrace :INFO
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 3bf45fb

Please sign in to comment.