Ravn::
SignalHandling
module
A module containing queued signal-handling logic
Refs: - github.com/ruby/ruby/blob/012faccf040344360801b0fa77e85f9c8a3a4b2c/doc/signals.rdoc
- IDLE_CHECK_INTERVAL
The number of seconds between checks for pending signals
Extension callback — add functionality to the given object
.
def self::extended( object )
super
object.instance_variable_set( :@selfpipe, nil )
object.instance_variable_set( :@signal_queue, nil )
object.instance_variable_set( :@signal_handler_thread, nil )
object.singleton_class.attr_reader :selfpipe
object.singleton_class.attr_reader :signal_queue
object.singleton_class.attr_reader :signal_handler_thread
unless object.respond_to?( :log )
object.extend( Loggability )
object.log_to( :ravn )
end
end
Returns true if the signal handler is running.
def handling_signals?
return self.signal_handler_thread&.alive?
end
ignore_signals( *signals )
Set all signal handlers to ignore.
def ignore_signals( *signals )
self.log.debug "Ignoring signals."
signals.each do |sig|
next if sig == :CHLD
Signal.trap( sig, :IGNORE )
end
end
reset_signal_traps( *signals )
Set the signal handlers back to their defaults.
def reset_signal_traps( *signals )
self.log.debug "Restoring default signal handlers."
signals.each do |sig|
Signal.trap( sig, :DEFAULT )
end
end
set_signal_traps( *signals )
Set up signal handlers for common signals that will shut down, restart, etc.
def set_signal_traps( *signals )
self.log.debug "Setting up queued signal handler traps."
signals.each do |sig|
Signal.trap( sig ) do
@signal_queue << sig
self.wake_up
end
end
end
Set up data structures for signal handling.
def set_up_signal_handling
self.log.info "Setting up queued signal handling."
reader, writer = IO.pipe
reader.close_on_exec = true
writer.close_on_exec = true
@selfpipe = { reader: reader, writer: writer }
@signal_queue = Concurrent::Array.new
end
simulate_signal( signal )
Simulate the receipt of the specified signal
(probably only useful in testing).
def simulate_signal( signal )
@signal_queue << signal.to_sym
self.wake_up
end
Start a handler for queued signals.
def start_signal_handler
self.log.info "Starting signal handler thread."
@signal_handler_thread = Thread.new do
Thread.current.name = "signal handler"
until self.selfpipe[:reader].closed?
self.wait_for_signals( IDLE_CHECK_INTERVAL )
end
end
end
Stop the signal handler thread.
def stop_signal_handler
self.log.info "Stopping the signal handler thread."
@signal_handler_thread.kill
@signal_handler_thread.join( 3 )
self.selfpipe[:reader].close
self.log.info "Signal handler thread done; resetting signal handlers."
end
wait_for_signals( timeout=nil )
The body of the signal handler. Wait for at least one signal to arrive and handle it, or timeout and return if a timeout
integer is provided. This should be called inside a loop, either in its own thread or in another loop that doesn’t block anywhere else. Returns true if a signal was handled, or false if a timeout occurred.
def wait_for_signals( timeout=nil )
fds = IO.select( [self.selfpipe[:reader]], [], [], timeout )
if fds
begin
rval = self.selfpipe[:reader].read_nonblock( 11 )
self.log.debug " read from the selfpipe: %p" % [ rval ]
rescue Errno::EAGAIN, Errno::EINTR => err
end
end
while sig = @signal_queue.shift
self.log.debug " got a queued signal: %p" % [ sig ]
self.handle_signal( sig )
end
return fds ? true : false
end
Wake the main thread up through the self-pipe. Note: since this is a signal-handler method, it needs to be re-entrant.
def wake_up
self.selfpipe[ :writer ].write_nonblock( '.' )
rescue Errno::EAGAIN
rescue Errno::EINTR
retry
end
with_signal_handler( *signals ) { || ... }
Wrap a block in signal-handling.
def with_signal_handler( *signals )
self.set_up_signal_handling
self.set_signal_traps( *signals )
self.start_signal_handler
return yield
ensure
self.stop_signal_handler
self.reset_signal_traps( *signals )
end