class EY::Serverside::Spawner

Public Class Methods

new() click to toggle source
# File lib/engineyard-serverside/spawner.rb, line 13
def initialize
  @poll_period = 0.5
  @children = []
end
run(cmd, shell, server = nil) click to toggle source
# File lib/engineyard-serverside/spawner.rb, line 7
def self.run(cmd, shell, server = nil)
  s = new
  s.add(cmd, shell, server)
  s.run.first
end

Public Instance Methods

add(cmd, shell, server = nil) click to toggle source
# File lib/engineyard-serverside/spawner.rb, line 18
def add(cmd, shell, server = nil)
  @children << Child.new(cmd, shell, server)
end
run() click to toggle source
# File lib/engineyard-serverside/spawner.rb, line 22
def run
  @child_by_fd = {}
  @child_by_pid = {}

  @children.each do |child|
    pid, stdout_fd, stderr_fd = child.spawn
    @child_by_pid[pid] = child
    @child_by_fd[stdout_fd] = child
    @child_by_fd[stderr_fd] = child
  end

  while @child_by_pid.any?
    process
    wait
  end

  @children.map { |child| child.result }
end

Protected Instance Methods

process() click to toggle source
# File lib/engineyard-serverside/spawner.rb, line 43
def process
  read_fds = @child_by_pid.values.map {|child| child.ios }.flatten.compact
  ra, _, _ = IO.select(read_fds, [], [], @poll_period)
  process_readable(ra) if ra
end
process_readable(ra) click to toggle source
# File lib/engineyard-serverside/spawner.rb, line 49
def process_readable(ra)
  ra.each do |fd|
    child = @child_by_fd[fd]
    if !child
      raise "Select returned unknown fd: #{fd.inspect}"
    end

    begin
      if buf = fd.sysread(4096)
        child.append_to_buffer(fd, buf)
      else
        raise "sysread() returned nil"
      end
    rescue SystemCallError, EOFError => e
      @child_by_fd.delete(fd)
      child.close(fd)
    end
  end
end
wait() click to toggle source
# File lib/engineyard-serverside/spawner.rb, line 69
def wait
  possible_children = true
  just_reaped = []
  while possible_children
    begin
      pid, status = Process::waitpid2(-1, Process::WNOHANG)
      if pid.nil?
        possible_children = false
      elsif child = @child_by_pid.delete(pid)
        child.finished status
        just_reaped << child
      elsif pid == -1
        # waitpid encountered an error (as defined in linux waitpid manpage)
        # apparently it can leak through ruby's waitpid abstraction
        raise "Fatal error encountered while waiting for a child process to exit. waitpid2 returned: [#{pid.inspect}, #{status.inspect}].\nExpected one of children: #{@child_by_pid.keys.inspect}"
      else
        raise "Unknown pid returned from waitpid2 => #{pid.inspect}, #{status.inspect}.\nExpected one of children: #{@child_by_pid.keys.inspect}"
      end
    rescue Errno::ECHILD
      possible_children = false
    end
  end
  # We may have waited on a child before reading all its output. Collect those missing bits. No blocking.
  if just_reaped.any?
    read_fds = just_reaped.map {|child| child.ios }.flatten.compact
    ra, _, _ = IO.select(read_fds, nil, nil, 0)
    process_readable(ra) if ra
  end
end