module CZTop::Reactor::SignalHandling

A mixin that adds methods for running a process with queued signal handling via a ZMQ PAIR socket.

require 'cztop'
require 'cztop/reactor'
require 'cztop/reactor/signal_handling'

class MyDaemon
    include CZTop::Reactor::SignalHandling

    def start
        @reactor = CZTop::Reactor.new
        @reactor.register( @socket, :read, &self.method(:handle_io_event) )
        self.with_signal_handler( @reactor, :HUP, :INT, :TERM ) do
            @reactor.start_polling( ignore_interrupts: true )
        end
    end

    def stop
        @reactor.stop_polling
    end

    def handle_signal( signal_name )
        case signal_name
        when :INT, :TERM, :HUP
            self.stop
        else
            super
        end
    end
end

With this mixin included, you can wrap a block with a call to with_signal_handler, and when a signal arrives, the handle_signal method will be called with the name of the signal.

Constants

SIGNAL_QUEUE_KEY

The name of the thread-local variable that stores pending signals.

Public Class Methods

included( mod ) click to toggle source

Inclusion callback – add Loggability to including classes.

Calls superclass method
# File lib/cztop/reactor/signal_handling.rb, line 51
def self::included( mod )
        super
        mod.extend( Loggability )
        mod.log_to( :cztop ) unless Loggability.log_host?( mod )
end

Public Instance Methods

simulate_signal( signal ) click to toggle source

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

# File lib/cztop/reactor/signal_handling.rb, line 75
def simulate_signal( signal )
        Thread.main[ SIGNAL_QUEUE_KEY ] << signal.to_sym
        self.wake_up
end
with_signal_handler( reactor, *signals ) { || ... } click to toggle source

Wrap a block in signal-handling.

# File lib/cztop/reactor/signal_handling.rb, line 59
def with_signal_handler( reactor, *signals )
        self.set_up_signal_handling( reactor )
        self.set_signal_traps( *signals )

        return yield

ensure
        self.log.debug "Going to reset signal traps..."
        self.reset_signal_traps( *signals )
        self.log.debug "Going to clean up signal handler..."
        self.clean_up_signal_handling( reactor )
end

Protected Instance Methods

clean_up_signal_handling( reactor ) click to toggle source

Tear down the data structures for signal handling

# File lib/cztop/reactor/signal_handling.rb, line 102
def clean_up_signal_handling( reactor )
        if @self_pipe
                self.log.info "Cleaning up signal handler self-pipe."
                reactor.unregister( @self_pipe[:reader] )

                @self_pipe[:writer].options.linger = 0
                @self_pipe[:writer].close
                @self_pipe[:reader].options.linger = 0
                @self_pipe[:reader].close
        else
                self.log.info "No self-pipe; skipping signal-handler cleanup."
        end

        Thread.main[ SIGNAL_QUEUE_KEY ].clear
end
handle_queued_signals( event ) click to toggle source

Look for any signals that arrived and handle them.

# File lib/cztop/reactor/signal_handling.rb, line 120
def handle_queued_signals( event )
        event.socket.wait
        while sig = Thread.main[ SIGNAL_QUEUE_KEY ].shift
                self.log.debug "  got a queued signal: %p" % [ sig ]
                self.handle_signal( sig )
        end
end
handle_signal( signal_name ) click to toggle source

Default signal-handler callback – this raises an exception by default.

# File lib/cztop/reactor/signal_handling.rb, line 130
def handle_signal( signal_name )
        raise NotImplementedError, "unhandled signal %s" % [ signal_name ]
end
ignore_signals( *signals ) click to toggle source

Set the traps for the specified signals to IGNORE.

# File lib/cztop/reactor/signal_handling.rb, line 154
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 ) click to toggle source

Set the traps for the specified signals to the default handler.

# File lib/cztop/reactor/signal_handling.rb, line 164
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 ) click to toggle source

Set up signal traps for the specified signals.

# File lib/cztop/reactor/signal_handling.rb, line 142
def set_signal_traps( *signals )
        self.log.debug "Setting up deferred signal handlers for signals: %p." % [ signals ]
        signals.each do |sig|
                Signal.trap( sig ) do
                        Thread.main[ SIGNAL_QUEUE_KEY ] << sig
                        self.wake_up
                end
        end
end
set_up_signal_handling( reactor ) click to toggle source

Set up data structures for signal handling.

# File lib/cztop/reactor/signal_handling.rb, line 86
def set_up_signal_handling( reactor )
        Thread.main[ SIGNAL_QUEUE_KEY ] = []

        endpoint = "inproc://signal-handler-%s" % [ SecureRandom.hex(8) ]
        @self_pipe = {
                reader: CZTop::Socket::PAIR.new( "@#{endpoint}" ),
                writer: CZTop::Socket::PAIR.new( ">#{endpoint}" )
        }

        # :TODO: Consider calling #set_unbounded on the PAIR sockets

        reactor.register( @self_pipe[:reader], :read, &self.method(:handle_queued_signals) )
end
wake_up() click to toggle source

Signal through the self-pipe that one or more signals has been queued.

# File lib/cztop/reactor/signal_handling.rb, line 136
def wake_up
        @self_pipe[:writer].signal( 1 )
end