module Sensu::Spawn

Public Class Methods

build_child_process(command) click to toggle source

Build a child process attached to a pipe, in order to capture its output (STDERR, STDOUT). The child process will be a platform dependent shell, that is responsible for executing the provided command.

@param [String] command to run. @return [Array] child object, pipe reader, pipe writer.

# File lib/sensu/spawn.rb, line 46
def build_child_process(command)
  reader, writer = IO.pipe
  shell = case RUBY_PLATFORM
  when /(ms|cyg|bcc)win|mingw|win32/
    ["cmd", "/c"]
  else
    ["sh", "-c"]
  end
  ChildProcess.posix_spawn = true
  shell_command = shell + [command]
  child = ChildProcess.build(*shell_command)
  child.io.stdout = child.io.stderr = writer
  child.leader = true
  [child, reader, writer]
end
child_process(command, options={}) click to toggle source

Create a child process, return its output (STDERR & STDOUT), and exit status. The child process will have its own process group, may accept data via STDIN, and have a timeout. ChildProcess Unix POSIX spawn (`start()`) is not thread safe, so a mutex is used to allow safe execution on Ruby runtimes with real threads (JRuby).

The child process timeout functionality needs to be re-worked, as it currenty allows for a deadlock, when the child output is greater than the OS max buffer size.

@param [String] command to run. @param [Hash] options to create a child process with. @option options [String] :data to write to STDIN. @option options [Integer] :timeout in seconds. @return [Array] child process output and exit status.

# File lib/sensu/spawn.rb, line 92
def child_process(command, options={})
  child, reader, writer = build_child_process(command)
  child.duplex = true if options[:data]
  @@mutex.synchronize do
    child.start
  end
  writer.close
  if options[:data]
    child.io.stdin.write(options[:data])
    child.io.stdin.close
  end
  if options[:timeout]
    child.poll_for_exit(options[:timeout])
    output = read_until_eof(reader)
  else
    output = read_until_eof(reader)
    child.wait
  end
  [output, child.exit_code]
rescue ChildProcess::TimeoutError
  child.stop rescue nil
  ["Execution timed out", 2]
rescue => error
  child.stop rescue nil
  ["Unexpected error: #{error}", 3]
end
process(command, options={}, &callback) click to toggle source

Spawn a child process. A maximum of 12 processes will be spawned at a time. The EventMachine reactor (loop) must be running for this method to work.

@param [String] command to run. @param [Hash] options to create a child process with. @option options [String] :data to write to STDIN. @option options [Integer] :timeout in seconds. @param [Proc] callback called when the child process exits,

its output and exit status are passed as parameters.
# File lib/sensu/spawn.rb, line 31
def process(command, options={}, &callback)
  create = Proc.new do
    child_process(command, options)
  end
  @process_worker ||= EM::Worker.new(:concurrency => 12)
  @process_worker.enqueue(create, callback)
end
read_until_eof(reader) click to toggle source

Read a stream/file until end of file (EOF).

@param [Object] reader to read contents of until EOF. @return [String] the stream/file contents.

# File lib/sensu/spawn.rb, line 66
def read_until_eof(reader)
  output = ""
  begin
    loop { output << reader.readpartial(8192) }
  rescue EOFError
  end
  reader.close
  output
end