lib/servolux/child.rb



#
#
class Servolux::Child

  attr_accessor :command
  attr_accessor :timeout
  attr_accessor :signals
  attr_accessor :suspend
  attr_reader :io
  attr_reader :pid

  #
  #
  def initialize( command, opts = {} )
    @command = command
    @timeout = opts.getopt :timeout
    @signals = opts.getopt :signals, %w[TERM QUIT KILL]
    @suspend = opts.getopt :suspend, 4
    @io = @pid = @thread = @timed_out = nil
  end

  #
  #
  def start( mode = 'r', &block )
    start_timeout_thread if @timeout

    @io  = IO::popen @command, mode
    @pid = @io.pid

    return block.call(@io) unless block.nil?
    self
  end

  #
  #
  def stop
    unless @thread.nil?
      t, @thread = @thread, nil
      t[:stop] = true
      t.wakeup.join if t.status
    end

    kill if alive?
    @io.close rescue nil
    @io = @pid = nil
    self
  end

  # Waits for the child process to exit and returns its exit status. The
  # global variable $? is set to a Process::Status object containing
  # information on the child process.
  #
  def wait( flags = 0 )
    return if @io.nil?
    Process.wait(@pid, flags)
    $?.exitstatus
  end

  # Returns +true+ if the child process is alive.
  #
  def alive?
    return if @io.nil?
    Process.kill(0, @pid)
    true
  rescue Errno::ESRCH, Errno::ENOENT
    false
  end

  # Returns +true+ if the child process was killed by the timeout thread.
  #
  def timed_out?
    @timed_out
  end


  private

  #
  #
  def kill
    return if @io.nil?

    existed = false
    @signals.each do |sig|
      begin
        Process.kill sig, @pid
        existed = true
      rescue Errno::ESRCH, Errno::ENOENT
        return(existed ? nil : true)
      end
      return true unless alive?
      sleep @suspend
      return true unless alive?
    end
    return !alive?
  end

  #
  #
  def start_timeout_thread
    @timed_out = false
    @thread = Thread.new(self) { |child|
      sleep @timeout
      unless Thread.current[:stop]
        if child.alive?
          child.instance_variable_set(:@timed_out, true)
          child.__send__(:kill)
        end
      end
    }
  end

end  # class Servolux::Child

# EOF