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

Perform a VM-wide thread stack trace dump on USR2 #263

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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 66 additions & 5 deletions lib/hutch/waiter.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
require 'hutch/logging'

module Hutch
# Signal-handling class.
#
# Currently, the signal USR2 performs a thread dump,
# while QUIT, TERM and INT all perform a graceful shutdown.
class Waiter
include Logging

SHUTDOWN_SIGNALS = %w(QUIT TERM INT).keep_if { |s| Signal.list.keys.include? s }.freeze
class ContinueProcessingSignals < RuntimeError
end

def self.supported_signals_of(list)
list.keep_if { |s| Signal.list.keys.include? s }
end

SHUTDOWN_SIGNALS = supported_signals_of(%w(QUIT TERM INT)).freeze
# We have chosen a JRuby-supported signal
USER_SIGNALS = supported_signals_of(%w(USR2)).freeze
REGISTERED_SIGNALS = (SHUTDOWN_SIGNALS + USER_SIGNALS).freeze

def self.wait_until_signaled
new.wait_until_signaled
Expand All @@ -14,28 +28,75 @@ def wait_until_signaled
self.sig_read, self.sig_write = IO.pipe

register_signal_handlers
wait_for_signal

sig = sig_read.gets.strip.downcase
logger.info "caught sig#{sig}, stopping hutch..."
begin
wait_for_signal

sig = sig_read.gets.strip
handle_signal(sig)
rescue ContinueProcessingSignals
retry
end
end

def handle_signal(sig)
raise ContinueProcessingSignals unless REGISTERED_SIGNALS.include?(sig)
if user_signal?(sig)
handle_user_signal(sig)
else
handle_shutdown_signal(sig)
end
end

# @raises ContinueProcessingSignals
def handle_user_signal(sig)
case sig
when 'USR2' then log_thread_backtraces
else raise "Assertion failed - unhandled signal: #{sig.inspect}"
end
raise ContinueProcessingSignals
end

def handle_shutdown_signal(sig)
logger.info "caught SIG#{sig}, stopping hutch..."
end

private

def log_thread_backtraces
logger.info 'Requested a VM-wide thread stack trace dump...'
Thread.list.each do |thread|
Copy link
Member

Choose a reason for hiding this comment

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

I suggest that we log a header such as "requested a VM-wide thread stack trace dump…"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed!

logger.info "Thread TID-#{thread.object_id.to_s(36)} #{thread['label']}"
logger.info backtrace_for(thread)
end
end

def backtrace_for(thread)
if thread.backtrace
thread.backtrace.join("\n")
else
'<no backtrace available>'
end
end

attr_accessor :sig_read, :sig_write

def wait_for_signal
IO.select([sig_read])
end

def register_signal_handlers
SHUTDOWN_SIGNALS.each do |sig|
REGISTERED_SIGNALS.each do |sig|
# This needs to be reentrant, so we queue up signals to be handled
# in the run loop, rather than acting on signals here
trap(sig) do
sig_write.puts(sig)
end
end
end

def user_signal?(sig)
USER_SIGNALS.include?(sig)
end
end
end
2 changes: 1 addition & 1 deletion spec/hutch/waiter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def start_kill_thread(signal)
context "a #{signal} signal is received" do
it "logs that hutch is stopping" do
expect(Hutch::Logging.logger).to receive(:info)
.with("caught sig#{signal.downcase}, stopping hutch...")
.with("caught SIG#{signal}, stopping hutch...")

start_kill_thread(signal)
described_class.wait_until_signaled
Expand Down