Issue report
Question 1: What is the problem?
Hey there 馃憢! I work at @DataDog on our continuous profiler for Ruby. We've had a couple of customers report issues when our profiler is used together with passenger.
TL;DR when Passenger starts it resets a bunch of signal handlers:
|
# Reset signal handlers to their default handler, and install some |
|
# special handlers for a few signals. The previous signal handlers |
|
# will be put back by calling revert_signal_handlers. |
|
def reset_signal_handlers |
|
Signal.list_trappable.each_key do |signal| |
|
begin |
|
prev_handler = trap(signal, DEFAULT) |
|
if prev_handler != DEFAULT |
|
@previous_signal_handlers[signal] = prev_handler |
|
end |
|
rescue ArgumentError |
|
# Signal cannot be trapped; ignore it. |
|
end |
|
end |
|
trap('HUP', IGNORE) |
|
PhusionPassenger.call_event(:after_installing_signal_handlers) |
|
end |
...including the SIGPROF signal handler (which is returned by Signal.list_trappable).
This breaks profiling Ruby apps because the following happens:
- Profiler starts running, installing a
SIGPROF signal handler used for data collection.
- Passenger resets signal handlers
- Profiler sends a
SIGPROF signal, causing the Ruby app to crash because it no longer has a signal handler for this signal.
To add a bit more context, using SIGPROF for profiling is a common approach, not just something weird we do at Datadog:
I suspect the solution would be to extend the existing list_trappable with a result.delete("PROF") so that SIGPROF signal handlers remain intact.
- What is the expected behavior?
Applications that have a SIGPROF signal handler installed and then receive a SIGPROF signal correctly handle it, rather than crashing.
- What is the actual behavior?
These applications crash (as in, Ruby process goes poof)
- How can we reproduce it? Please try to provide a sample application (or Virtual Machine) demonstrating the issue. Otherwise, if we can't reproduce it, we might have to ask you a number of followup questions or run certain commands to try and figure out the problem.
Here's an example pure-Ruby reproducer config.ru:
puts "Installing SIGPROF signal handler!"
Signal.trap("PROF") do
puts " !! Got sigprof!"
end
puts "Testing signal handler!"
Process.kill("PROF", Process.pid)
puts "Running rack app!"
app = ->(env) {
puts " ** Got request!"
if env['REQUEST_URI'].start_with?('/signal')
puts "Sending signal to myself!"
Process.kill("PROF", Process.pid)
end
[200, {}, ['Hello, World!']]
}
run app
Here's how it looks in puma:
$ bundle exec puma
Puma starting in single mode...
* Puma version: 6.1.1 (ruby 3.2.2-p53) ("The Way Up")
* Min threads: 0
* Max threads: 5
* Environment: development
* PID: 203590
Installing SIGPROF signal handler!
Testing signal handler!
!! Got sigprof!
Running rack app!
* Listening on http://0.0.0.0:9292
Use Ctrl-C to stop
** Got request! # <-- Me hitting /signal
Sending signal to myself!
!! Got sigprof! # <-- Signal handler still working fine
** Got request!
Sending signal to myself!
!! Got sigprof!
** Got request!
and in passenger:
$ bundle exec passenger start
=============== Phusion Passenger(R) Standalone web server started ===============
PID file: passenger.3000.pid
Log file: passenger.3000.log
Environment: development
Accessible via: http://0.0.0.0:3000/
You can stop Phusion Passenger(R) Standalone by pressing Ctrl-C.
Problems? Check https://www.phusionpassenger.com/library/admin/standalone/troubleshooting/
===============================================================================
[ N 2023-07-25 10:13:12.5640 204259/T5 age/Cor/SecurityUpdateChecker.h:519 ]: Security update check: no update found (next check in 24 hours)
App 204333 output: Installing SIGPROF signal handler!
App 204333 output: Testing signal handler!
App 204333 output: !! Got sigprof!
App 204333 output: Running rack app!
App 204368 output: ** Got request! # <-- Regular request
App 204368 output: ** Got request! # <-- I hit /signal
App 204368 output: Sending signal to myself!
[ W 2023-07-25 10:13:27.9117 204259/Te age/Cor/Con/InternalUtils.cpp:96 ]: [Client 4-1] Sending 502 response: application did not send a complete response
[ W 2023-07-25 10:13:30.0219 204259/T3 age/Cor/App/Poo/AnalyticsCollection.cpp:101 ]: Process (pid=204368, group=example (development)) no longer exists! Detaching it from the pool.
[ N 2023-07-25 10:13:30.0220 204259/T3 age/Cor/CoreMain.cpp:1146 ]: Checking whether to disconnect long-running connections for process 204368, application example (development)
Question 2: Passenger version and integration mode:
- Passenger
6.0.18 (Latest version at time of writing)
- Standalone mode
Question 3: OS or Linux distro, platform (including version):
- Ubuntu 22.04.2 LTS, x86_64
Question 4: Passenger installation method:
[x] RubyGems + Gemfile
Question 5: Your app's programming language (including any version managers) and framework (including versions):
- Ruby 3.2.2
- rvm 1.29.12-next (master)
Question 6: Are you using a PaaS and/or containerization? If so which one?
Can be reproduced locally.
Question 7: Anything else about your setup that we should know?
Happy to submit a PR :)
We strive for quality and appreciate you taking the time to submit a report! Please note that if you want guaranteed response times and priority issue support we encourage you to join our enterprise customer base. They also provide us with the means to continue our high level of open source support!
Issue report
Question 1: What is the problem?
Hey there 馃憢! I work at @DataDog on our continuous profiler for Ruby. We've had a couple of customers report issues when our profiler is used together with passenger.
TL;DR when Passenger starts it resets a bunch of signal handlers:
passenger/src/ruby_supportlib/phusion_passenger/request_handler.rb
Lines 329 to 345 in 3e0794a
...including the
SIGPROFsignal handler (which is returned bySignal.list_trappable).This breaks profiling Ruby apps because the following happens:
SIGPROFsignal handler used for data collection.SIGPROFsignal, causing the Ruby app to crash because it no longer has a signal handler for this signal.To add a bit more context, using
SIGPROFfor profiling is a common approach, not just something weird we do at Datadog:I suspect the solution would be to extend the existing
list_trappablewith aresult.delete("PROF")so thatSIGPROFsignal handlers remain intact.Applications that have a
SIGPROFsignal handler installed and then receive aSIGPROFsignal correctly handle it, rather than crashing.These applications crash (as in, Ruby process goes poof)
Here's an example pure-Ruby reproducer
config.ru:Here's how it looks in puma:
and in passenger:
Question 2: Passenger version and integration mode:
6.0.18(Latest version at time of writing)Question 3: OS or Linux distro, platform (including version):
Question 4: Passenger installation method:
[x] RubyGems + Gemfile
Question 5: Your app's programming language (including any version managers) and framework (including versions):
Question 6: Are you using a PaaS and/or containerization? If so which one?
Can be reproduced locally.
Question 7: Anything else about your setup that we should know?
Happy to submit a PR :)
We strive for quality and appreciate you taking the time to submit a report! Please note that if you want guaranteed response times and priority issue support we encourage you to join our enterprise customer base. They also provide us with the means to continue our high level of open source support!