module Sensu::Spawn

Constants

POSIX_SPAWN_PLATFORMS

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 83
def build_child_process(command)
  reader, writer = IO.pipe
  shell = case
  when on_windows?
    ["cmd", "/c"]
  else
    ["sh", "-c"]
  end
  ChildProcess.posix_spawn = posix_spawn?
  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).

Using stdlib's Timeout instead of child.poll_for_exit to avoid 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 143
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
  output = ""
  if options[:timeout]
    Timeout::timeout(options[:timeout], ChildProcess::TimeoutError) do
      output = write_and_read(child.io.stdin, reader, options[:data])
      child.wait
    end
  else
    output = write_and_read(child.io.stdin, reader, options[:data])
    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
on_windows?() click to toggle source

Determine if the current platform is Windows.

@return [TrueClass, FalseClass]

# File lib/sensu/spawn.rb, line 71
def on_windows?
  return @on_windows unless @on_windows.nil?
  @on_windows = ChildProcess.windows?
end
posix_spawn?() click to toggle source

Determine if POSIX Spawn is used to create child processes on the current platform. ChildProcess supports POSIX Spawn for several platforms (OSs & architectures), however, Sensu only enables the use of POSIX Spawn on a select few.

@return [TrueClass, FalseClass]

# File lib/sensu/spawn.rb, line 63
def posix_spawn?
  return @posix_spawn unless @posix_spawn.nil?
  @posix_spawn = POSIX_SPAWN_PLATFORMS.include?(ChildProcess.os)
end
process(command, options={}, &callback) click to toggle source

Spawn a child process. 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 49
def process(command, options={}, &callback)
  create = Proc.new do
    child_process(command, options)
  end
  setup(options) unless @process_worker
  @process_worker.enqueue(create, callback)
end
setup(options={}) click to toggle source

Setup a spawn process worker, to limit the number of concurrent child processes allowed at one time. This method creates the spawn process worker instance variable: `@process_worker`.

@param [Hash] options to create a process worker with. @option options [Integer] :limit max number of child processes

at a time.
# File lib/sensu/spawn.rb, line 35
def setup(options={})
  limit = options[:limit] || 12
  @process_worker ||= EM::Worker.new(:concurrency => limit)
end
write_and_read(writer, reader, data) click to toggle source

Write data to a stream/file and read a stream/file until end of file (EOF).

@param writer [Object] to write data to (optional). @param reader [Object] to read contents of until EOF. @param data [String] to be written to writer. @return [String] the reader stream/file contents.

# File lib/sensu/spawn.rb, line 106
def write_and_read(writer, reader, data)
  buffer = (data || "").dup
  output = ""
  loop do
    unless buffer.empty?
      writer.write(buffer.slice!(0, 8191))
      writer.close if buffer.empty?
    end
    begin
      readable, _ = IO.select([reader], nil, nil, 0)
      if readable || buffer.empty?
        output << reader.readpartial(8192)
      end
    rescue EOFError
      reader.close
      break
    end
  end
  output
end