Class: Servolux::Child

Inherits:
Object
  • Object
show all
Defined in:
lib/servolux/child.rb

Overview

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

Instance Attribute Summary (collapse)

Instance Method Summary (collapse)

Constructor Details

- (Child) initialize(opts = {}) {|_self| ... }

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

Parameters:

  • opts (Hash) (defaults to: {})

    a customizable set of options

Options Hash (opts):

  • :command (String)

    The command that will be executed via IO#popen.

  • :timeout (Numeric) — default: 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.

  • :signals (Array<String, Integer>) — default: ['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.

  • :suspend (Numeric) — default: 4

    The number of seconds to wait for the child process to respond to a signal before trying the next one in the list.

Yields:

  • (_self)

Yield Parameters:



69
70
71
72
73
74
75
76
# 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

Instance Attribute Details

- (Object) command

Returns the value of attribute command



42
43
44
# File 'lib/servolux/child.rb', line 42

def command
  @command
end

- (Object) io (readonly)

Returns the value of attribute io



46
47
48
# File 'lib/servolux/child.rb', line 46

def io
  @io
end

- (Object) pid (readonly)

Returns the value of attribute pid



47
48
49
# File 'lib/servolux/child.rb', line 47

def pid
  @pid
end

- (Object) signals

Returns the value of attribute signals



44
45
46
# File 'lib/servolux/child.rb', line 44

def signals
  @signals
end

- (Object) suspend

Returns the value of attribute suspend



45
46
47
# File 'lib/servolux/child.rb', line 45

def suspend
  @suspend
end

- (Object) timeout

Returns the value of attribute timeout



43
44
45
# File 'lib/servolux/child.rb', line 43

def timeout
  @timeout
end

Instance Method Details

- (Boolean) alive?

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

Returns:

  • (Boolean)


162
163
164
165
166
167
168
169
# File 'lib/servolux/child.rb', line 162

def alive?
  return if @io.nil?
  wait(Process::WNOHANG|Process::WUNTRACED)
  Process.kill(0, @pid)
  true
rescue Errno::ESRCH, Errno::ENOENT
  false
end

- (IO) start(mode = 'r') {|IO| ... }

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.

Parameters:

  • mode (String) (defaults to: 'r')

    The mode flag used to open the child process via IO#popen.

Yields:

  • (IO)

    Execute the block of call passing in the communication pipe with the child process.

Yield Returns:

  • Returns the result of the block.

Returns:

  • (IO)

    The communication pipe with the child process or the return value from the block if one was given.



95
96
97
98
99
100
101
102
103
104
# 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

- (Object) stop

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.

Returns:

  • self



116
117
118
119
120
121
122
123
124
125
126
127
# 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 rescue nil
  @io = nil
  self
end

- (Boolean) timed_out?

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

Returns:

  • (Boolean)


175
176
177
# File 'lib/servolux/child.rb', line 175

def timed_out?
  @timed_out
end

- (Integer?) wait(flags = 0)

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

Parameters:

  • flags (Integer) (defaults to: 0)

    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.

Returns:

  • (Integer, nil)

    The exit status of the child process or nil if the child process is not running.



151
152
153
154
155
# File 'lib/servolux/child.rb', line 151

def wait( flags = 0 )
  return if @io.nil?
  _, @status = Process.wait2(@pid, flags) unless @status
  exitstatus
end