lib/heroku/executor.rb in heroku-commander-0.1.0 vs lib/heroku/executor.rb in heroku-commander-0.2.0

- old
+ new

@@ -1,81 +1,111 @@ module Heroku class Executor class Terminate < StandardError + + attr_accessor :timeout + + def initialize(timeout = 0) + @timeout = timeout + end end class << self # Executes a command and yields output line-by-line. def run(cmd, options = {}, &block) lines = [] + running_pid = nil logger = options[:logger] logger.debug "Running: #{cmd}" if logger PTY.spawn(cmd) do |r, w, pid| + running_pid = pid logger.debug "Started: #{pid}" if logger terminated = false begin r.sync = true - r.each do |line| - line.strip! if line - logger.debug "#{pid}: #{line}" if logger - if block_given? - yield line - end - lines << line - end - rescue Heroku::Executor::Terminate - logger.debug "Terminating #{pid}." if logger - Process.kill("TERM", pid) + read_from(r, pid, options, lines, &block) + rescue Heroku::Executor::Terminate => e + logger.debug "Waiting: #{e.timeout} second(s) to terminate #{pid}" if logger + terminate_process! pid, e.timeout terminated = true rescue Errno::EIO, IOError => e - logger.debug "#{e.class}: #{e.message}" if logger + # ignore rescue PTY::ChildExited => e logger.debug "Terminated: #{pid}" if logger - terminted = true + terminated = true raise e ensure unless terminated + # wait for process logger.debug "Waiting: #{pid}" if logger - Process.wait(pid) rescue Errno::ECHILD + ::Process.wait(pid) end end end - check_exit_status! cmd, $?.exitstatus, lines + check_exit_status! cmd, running_pid, $?.exitstatus, lines lines rescue Errno::ECHILD => e - logger.debug "#{e.class}: #{e.message}" if logger - check_exit_status! cmd, $?.exitstatus, lines + check_exit_status! cmd, running_pid, $?.exitstatus, lines lines rescue PTY::ChildExited => e - logger.debug "#{e.class}: #{e.message}" if logger - check_exit_status! cmd, $!.status.exitstatus, lines + check_exit_status! cmd, running_pid, $!.status.exitstatus, lines lines rescue Heroku::Commander::Errors::Base => e logger.debug "Error: #{e.problem}" if logger raise rescue Exception => e logger.debug "#{e.class}: #{e.respond_to?(:problem) ? e.problem : e.message}" if logger raise Heroku::Commander::Errors::CommandError.new({ :cmd => cmd, + :pid => running_pid, :status => $?.exitstatus, :message => e.message, :inner_exception => e, :lines => lines }) end private - def check_exit_status!(cmd, status, lines = nil) + def read_from(r, pid, options, lines, &block) + logger = options[:logger] + while ! r.eof do + line = r.readline + line.strip! if line + logger.debug "#{pid}: #{line}" if logger + if block_given? + yield line + end + lines << line + end + end + + def check_exit_status!(cmd, pid, status, lines = nil) return if ! status || status == 0 raise Heroku::Commander::Errors::CommandError.new({ :cmd => cmd, + :pid => pid, :status => status, :message => "The command #{cmd} failed with exit status #{status}.", :lines => lines }) + end + + def terminate_process!(pid, timeout) + if timeout + # Delay terminating of the process, usually to let the output flush. + Thread.new(pid, timeout) do |pid, timeout| + begin + sleep(timeout) if timeout + ::Process.kill("TERM", pid) + rescue + end + end + else + ::Process.kill("TERM", pid) + end end end end end