Ravn::

SignalHandling module

A module containing queued signal-handling logic

Refs: - github.com/ruby/ruby/blob/012faccf040344360801b0fa77e85f9c8a3a4b2c/doc/signals.rdoc

Constants

IDLE_CHECK_INTERVAL

The number of seconds between checks for pending signals

Public Class Methods

extended( object )

Extension callback — add functionality to the given object.

# File lib/ravn/signal_handling.rb, line 20
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

Public Instance Methods

handling_signals?()

Returns true if the signal handler is running.

# File lib/ravn/signal_handling.rb, line 39
def handling_signals?
        return self.signal_handler_thread&.alive?
end
ignore_signals( *signals )

Set all signal handlers to ignore.

# File lib/ravn/signal_handling.rb, line 151
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.

# File lib/ravn/signal_handling.rb, line 161
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.

# File lib/ravn/signal_handling.rb, line 139
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_signal_handling()

Set up data structures for signal handling.

# File lib/ravn/signal_handling.rb, line 59
def set_up_signal_handling
        self.log.info "Setting up queued signal handling."

        # Self-pipe for deferred signal-handling (ala djb:
        # http://cr.yp.to/docs/selfpipe.html)
        reader, writer       = IO.pipe
        reader.close_on_exec = true
        writer.close_on_exec = true
        @selfpipe            = { reader: reader, writer: writer }

        # Set up a global signal queue
        @signal_queue = Concurrent::Array.new
end
simulate_signal( signal )

Simulate the receipt of the specified signal (probably only useful in testing).

# File lib/ravn/signal_handling.rb, line 171
def simulate_signal( signal )
        @signal_queue << signal.to_sym
        self.wake_up
end
start_signal_handler()

Start a handler for queued signals.

# File lib/ravn/signal_handling.rb, line 116
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_signal_handler()

Stop the signal handler thread.

# File lib/ravn/signal_handling.rb, line 129
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.

# File lib/ravn/signal_handling.rb, line 79
def wait_for_signals( timeout=nil )

        # Wait on the selfpipe for signals
        # self.log.debug "  waiting for the selfpipe"
        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
                        # ignore
                end
        end

        # Look for any signals that arrived and handle them
        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_up()

Wake the main thread up through the self-pipe. Note: since this is a signal-handler method, it needs to be re-entrant.

# File lib/ravn/signal_handling.rb, line 105
def wake_up
        self.selfpipe[ :writer ].write_nonblock( '.' )
rescue Errno::EAGAIN
        # Ignore.
rescue Errno::EINTR
        # Repeated signal. :TODO: Does this need a counter?
        retry
end
with_signal_handler( *signals ) { || ... }

Wrap a block in signal-handling.

# File lib/ravn/signal_handling.rb, line 45
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