Sha256: 121f16f714652a8d551b63ede835263ca556c07afb16c090a8a9e4071a3b10d1

Contents?: true

Size: 1.63 KB

Versions: 1

Compression:

Stored size: 1.63 KB

Contents

# frozen_string_literal: true

module Sigurd
  # Class that takes any object with a "start" and "stop" method and catches
  # signals to ask them to stop nicely.
  class SignalHandler
    SIGNALS = %i(INT TERM QUIT).freeze

    # Takes any object that responds to the `start` and `stop` methods.
    # @param runner[#start, #stop]
    def initialize(runner)
      @signal_queue = []
      @reader, @writer = IO.pipe
      @runner = runner
    end

    # Run the runner.
    def run!
      setup_signals
      @runner.start

      loop do
        case signal_queue.pop
        when *SIGNALS
          @runner.stop
          break
        else
          ready = IO.select([reader, writer])

          # drain the self-pipe so it won't be returned again next time
          reader.read_nonblock(1) if ready[0].include?(reader)
        end
      end
    end

  private

    attr_reader :reader, :writer, :signal_queue, :executor

    # https://stackoverflow.com/questions/29568298/run-code-when-signal-is-sent-but-do-not-trap-the-signal-in-ruby
    def prepend_handler(signal)
      previous = Signal.trap(signal) do
        previous = -> { raise SignalException, signal } unless previous.respond_to?(:call)
        yield
        previous.call
      end
    end

    # Trap signals using the self-pipe trick.
    def setup_signals
      at_exit { @runner&.stop }
      SIGNALS.each do |signal|
        prepend_handler(signal) do
          unblock(signal)
        end
      end
    end

    # Save the signal to the queue and continue on.
    # @param signal [Symbol]
    def unblock(signal)
      writer.write_nonblock('.')
      signal_queue << signal
    end
  end
end

Version data entries

1 entries across 1 versions & 1 rubygems

Version Path
sigurd-0.0.1 lib/sigurd/signal_handler.rb