class ExecApp

Run an application on the client.

Borrows from Open3

Attributes

clean_exit[R]
pid[R]

Public Class Methods

[](id) click to toggle source

Return an application instance based on its ID

@param [String] id of the application to return

# File lib/omf_common/exec_app.rb, line 47
def self.[](id)
  app = @@all_apps[id]
  warn "Unknown application '#{id}/#{id.class}'" if app.nil?
  return app
end
has?(id) click to toggle source
# File lib/omf_common/exec_app.rb, line 53
def self.has?(id)
  @@all_apps.has_key?(id)
end
new(id, cmd, map_std_err_to_out = false, working_directory = nil, &observer) click to toggle source

Run an application ‘cmd’ in a separate thread and monitor its stdout. Also send status reports to the ‘observer’ by calling its “call(eventType, appId, message”)“

@param id ID of application (used for reporting) @param observer Observer of application’s progress @param cmd Command path and args @param map_std_err_to_out If true report stderr as stdin [false]

# File lib/omf_common/exec_app.rb, line 92
def initialize(id, cmd, map_std_err_to_out = false, working_directory = nil, &observer)

  @id = id || self.object_id
  @observer = observer
  @@all_apps[@id] = self
  @exit_status = nil
  @threads = []

  pw = IO::pipe   # pipe[0] for read, pipe[1] for write
  pr = IO::pipe
  pe = IO::pipe

  logger.debug "Starting application '#{@id}' - cmd: '#{cmd}'"
  #@observer.call(:STARTED, id, cmd)
  call_observer(:STARTED, cmd)
  @pid = fork {
    # child will remap pipes to std and exec cmd
    pw[1].close
    STDIN.reopen(pw[0])
    pw[0].close

    pr[0].close
    STDOUT.reopen(pr[1])
    pr[1].close

    pe[0].close
    STDERR.reopen(pe[1])
    pe[1].close

    begin
      pgid = Process.setsid # Create a new process group
                            # which includes all potential child processes
      STDOUT.puts "INTERNAL WARNING: Assuming process_group_id == pid" unless pgid == $$
      Dir.chdir working_directory if working_directory
      exec(cmd)
    rescue => ex
      cmd = cmd.join(' ') if cmd.kind_of?(Array)
      STDERR.puts "exec failed for '#{cmd}' (#{$!}): #{ex}"
    end
    # Should never get here
    exit!
  }

  pw[0].close
  pr[1].close
  pe[1].close
  monitor_pipe(:stdout, pr[0])
  monitor_pipe(map_std_err_to_out ? :stdout : :stderr, pe[0])
  # Create thread which waits for application to exit
  @threads << Thread.new(id, @pid) do |id, pid|
    Process.waitpid(pid)
    # Exit status is sometimes nil (OSX 10.8, ping)
    @exit_status = $?.exitstatus || 0
    if @exit_status > 127
      @exit_status = 128 - @exit_status
    end
    @@all_apps.delete(@id)
    # app finished
    if (@exit_status == 0) || @clean_exit
      logger.debug "Application '#{@id}' finished"
    else
      logger.debug "Application '#{@id}' failed (code=#{@exit_status})"
    end
  end
  @stdin = pw[1]

  # wait for done in yet another thread
  Thread.new do
    @threads.each {|t| t.join }
    call_observer("EXIT", @exit_status)
  end
  logger.debug "Application is running with PID #{@pid}"
end
signal_all(signal = 'KILL') click to toggle source
# File lib/omf_common/exec_app.rb, line 57
def self.signal_all(signal = 'KILL')
  @@all_apps.each_value { |app| app.signal(signal) }
end

Public Instance Methods

signal(signal = 'KILL') click to toggle source
# File lib/omf_common/exec_app.rb, line 76
def signal(signal = 'KILL')
  logger.debug "Sending signal '#{signal}' to app '#{@id}' with pid #{@pid}"
  @clean_exit = true
  Process.kill(signal, -1 * @pid) # we are sending to the entire process group
end
stdin(line) click to toggle source
# File lib/omf_common/exec_app.rb, line 70
def stdin(line)
  logger.debug "Writing '#{line}' to app '#{@id}' with pid #{@pid}"
  @stdin.write("#{line}\n")
  @stdin.flush
end

Private Instance Methods

call_observer(event_type, msg) click to toggle source
# File lib/omf_common/exec_app.rb, line 193
def call_observer(event_type, msg)
  return unless @observer
  begin
    @observer.call(event_type, @id, msg)
  rescue Exception => ex
    logger.warn "Exception while calling observer '#{@observer}': #{ex}"
    logger.debug "#{ex}\n\t#{ex.backtrace.join("\n\t")}"
  end
end
monitor_pipe(name, pipe) click to toggle source

Create a thread to monitor the process and its output and report that back to the server

@param name Name of app stream to monitor (should be :stdout, :stderr) @param pipe Pipe to read from

# File lib/omf_common/exec_app.rb, line 175
def monitor_pipe(name, pipe)
  @threads << Thread.new() do
    begin
      while true do
        s = pipe.readline.chomp
        call_observer(name.to_s.upcase, s)
      end
    rescue EOFError
      # do nothing
    rescue  => err
      logger.error "monitorApp(#{@id}): #{err}"
      logger.debug "#{err}\n\t#{err.backtrace.join("\n\t")}"
    ensure
      pipe.close
    end
  end
end