class Servolux::Child

Synopsis

Manage a child process spawned via IO#popen and provide a timeout mechanism to kill the process after some amount time.

Details

Ruby provides the IO#popen method to spawn a child process and return an IO instance connected to the child’s stdin and stdout (with stderr redirected to stdout). The Servolux::Child class adds to this a timeout thread that will signal the child process after some number of seconds. If the child exits cleanly before the timeout expires then no signals are sent to the child.

A list of signals can be provided which will be sent in succession to the child until one of them causes the child to exit. The current Ruby thread suspends for a few seconds to allow each signal to be processed by the child. By default these signals are SIGTERM, SIGQUIT, SIGKILL and the time to wait between signals is four seconds.

The stop method is used to stop the child process (if running) and to reset the state of the Child instance so that it can be started again. Stopping the Child instance closes the IO between parent and child process.

The wait method is used to wait for the child process to exit. The Process::Status object is retrieved by the Child and stored as an instance variable. The exitstatus method (and the other process related methods) will return non-nil values after the wait method is called.

Examples

child = Servolux::Child.new(:command => 'sleep 120', :timeout => 10)
child.start
child.wait

child.timed_out?    #=> true
child.signaled?     #=> true
child.exitstatus    #=> nil

Attributes

command[RW]
io[R]
pid[R]
signals[RW]
suspend[RW]
timeout[RW]

Public Class Methods

new( opts = {} ) { |self| ... } click to toggle source

Create a new Child that will execute and manage the command string as a child process.

@option opts [String] :command

The command that will be executed via IO#popen.

@option opts [Numeric] :timeout (nil)

The number of seconds to wait before terminating the child process.
No action is taken if the child process exits normally before the
timeout expires.

@option opts [Array<String, Integer>] :signals ([‘TERM’, ‘QUIT’, ‘KILL’])

A list of signals that will be sent to the child process when the
timeout expires. The signals increase in severity with SIGKILL being
the signal of last resort.

@option opts [Numeric] :suspend (4)

The number of seconds to wait for the child process to respond to a
signal before trying the next one in the list.
# File lib/servolux/child.rb, line 69
def initialize( opts = {} )
  @command = opts.fetch(:command, nil)
  @timeout = opts.fetch(:timeout, nil)
  @signals = opts.fetch(:signals, %w[TERM QUIT KILL])
  @suspend = opts.fetch(:suspend, 4)
  @io = @pid = @status = @thread = @timed_out = nil
  yield self if block_given?
end

Public Instance Methods

alive?() click to toggle source

Returns true if the child process is alive. Returns nil if the child process has not been started.

@return [Boolean]

# File lib/servolux/child.rb, line 162
def alive?
  return if @pid.nil?
  wait(Process::WNOHANG|Process::WUNTRACED)
  Process.kill(0, @pid)
  true
rescue Errno::ESRCH, Errno::ENOENT
  false
end
start( mode = 'r', &block ) click to toggle source

Runs the command string as a subprocess; the subprocess’s standard input and output will be connected to the returned IO object. The default mode for the new file object is “r”, but mode may be set to any of the modes listed in the description for class IO.

If a block is given, Ruby will run the command as a child connected to Ruby with a pipe. Ruby’s end of the pipe will be passed as a parameter to the block. In this case the value of the block is returned.

@param [String] mode The mode flag used to open the child process via

IO#popen.

@yield [IO] Execute the block of call passing in the communication pipe

with the child process.

@yieldreturn Returns the result of the block. @return [IO] The communication pipe with the child process or the return

value from the block if one was given.
# File lib/servolux/child.rb, line 95
def start( mode = 'r', &block )
  start_timeout_thread if @timeout

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

  return block.call(@io) unless block.nil?
  @io
end
stop() click to toggle source

Stop the child process if it is alive. A sequence of signals are sent to the process until it dies with SIGKILL being the signal of last resort.

After this method returns, the IO pipe to the child will be closed and the stored child PID is set to nil. The start method can be safely called again.

@return self

# File lib/servolux/child.rb, line 116
def stop
  unless @thread.nil?
    t, @thread = @thread, nil
    t[:stop] = true
    t.wakeup.join if t.status
  end

  kill if alive?
  @io.close unless @io.nil? || @io.closed?
  @io = nil
  self
end
timed_out?() click to toggle source

Returns true if the child process was killed by the timeout thread.

@return [Boolean]

# File lib/servolux/child.rb, line 175
def timed_out?
  @timed_out
end
wait( flags = 0 ) click to toggle source

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.

You can get more information about how the child status exited by calling the following methods on the piper instance:

* coredump?
* exited?
* signaled?
* stopped?
* success?
* exitstatus
* stopsig
* termsig

@param [Integer] flags Bit flags that will be passed to the system level

wait call. See the Ruby core documentation for Process#wait for more
information on these flags.

@return [Integer, nil] The exit status of the child process or nil if

the child process is not running.
# File lib/servolux/child.rb, line 151
def wait( flags = 0 )
  return if @pid.nil?
  _, @status = Process.wait2(@pid, flags) unless @status
  exitstatus
end