diff --git a/docs/signals.md b/docs/signals.md index 3625c72bda..c03bdf06e9 100644 --- a/docs/signals.md +++ b/docs/signals.md @@ -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 diff --git a/lib/puma/launcher.rb b/lib/puma/launcher.rb index 20a34c1a4e..79380af61c 100644 --- a/lib/puma/launcher.rb +++ b/lib/puma/launcher.rb @@ -456,12 +456,16 @@ 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 @@ -469,6 +473,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) diff --git a/test/test_integration_cluster.rb b/test/test_integration_cluster.rb index a7f58f84ec..dbdd0ec2ba 100644 --- a/test/test_integration_cluster.rb +++ b/test/test_integration_cluster.rb @@ -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 @@ -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)