Manage a child process spawned via IO#popen and provide a timeout mechanism to kill the process after some amount time.
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.
child = Servolux::Child.new(:command => 'sleep 120', :timeout => 10) child.start child.wait child.timed_out? #=> true child.signaled? #=> true child.exitstatus #=> nil
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
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
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 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
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
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