lib/tty/command/process_runner.rb in tty-command-0.7.0 vs lib/tty/command/process_runner.rb in tty-command-0.8.0
- old
+ new
@@ -21,11 +21,12 @@
# @api private
def initialize(cmd, printer, &block)
@cmd = cmd
@timeout = cmd.options[:timeout]
@input = cmd.options[:input]
- @signal = cmd.options[:signal] || :TERM
+ @signal = cmd.options[:signal] || "SIGKILL"
+ @binmode = cmd.options[:binmode]
@printer = printer
@block = block
end
# Execute child process
@@ -38,25 +39,23 @@
#
# @api public
def run!
@printer.print_command_start(cmd)
start = Time.now
- runtime = 0.0
pid, stdin, stdout, stderr = ChildProcess.spawn(cmd)
# no input to write, close child's stdin pipe
stdin.close if (@input.nil? || @input.empty?) && !stdin.nil?
- readers = [stdout, stderr]
writers = [@input && stdin].compact
while writers.any?
- ready_readers, ready_writers = IO.select(readers, writers, [], @timeout)
- raise TimeoutExceeded if ready_readers.nil? || ready_writers.nil?
+ ready = IO.select(nil, writers, writers, @timeout)
+ raise TimeoutExceeded if ready.nil?
- write_stream(ready_writers, writers)
+ write_stream(ready[1], writers)
end
stdout_data, stderr_data = read_streams(stdout, stderr)
status = waitpid(pid)
@@ -65,10 +64,14 @@
@printer.print_command_exit(cmd, status, runtime)
Result.new(status, stdout_data, stderr_data, runtime)
ensure
[stdin, stdout, stderr].each { |fd| fd.close if fd && !fd.closed? }
+ if pid # Ensure no zombie processes
+ ::Process.detach(pid)
+ terminate(pid)
+ end
end
# Stop a process marked by pid
#
# @param [Integer] pid
@@ -78,10 +81,13 @@
::Process.kill(@signal, pid) rescue nil
end
private
+ # The buffer size for reading stdout and stderr
+ BUFSIZE = 3 * 1024
+
# @api private
def handle_timeout(runtime)
return unless @timeout
t = @timeout - runtime
@@ -124,17 +130,17 @@
# @api private
def read_streams(stdout, stderr)
stdout_data = []
stderr_data = Truncator.new
- out_buffer = -> (line) {
+ out_buffer = ->(line) {
stdout_data << line
@printer.print_command_out_data(cmd, line)
@block.(line, nil) if @block
}
- err_buffer = -> (line) {
+ err_buffer = ->(line) {
stderr_data << line
@printer.print_command_err_data(cmd, line)
@block.(nil, line) if @block
}
@@ -142,38 +148,48 @@
stderr_thread = read_stream(stderr, err_buffer)
stdout_thread.join
stderr_thread.join
- [stdout_data.join, stderr_data.read]
+ encoding = @binmode ? Encoding::BINARY : Encoding::UTF_8
+
+ [
+ stdout_data.join.force_encoding(encoding),
+ stderr_data.read.dup.force_encoding(encoding)
+ ]
end
def read_stream(stream, buffer)
Thread.new do
Thread.current[:cmd_start] = Time.now
- begin
- while (line = stream.gets)
- buffer.(line)
+ readers = [stream]
- # control total time spent reading
- runtime = Time.now - Thread.current[:cmd_start]
- handle_timeout(runtime)
+ while readers.any?
+ ready = IO.select(readers, nil, readers, @timeout)
+ raise TimeoutExceeded if ready.nil?
+
+ ready[0].each do |reader|
+ begin
+ line = reader.readpartial(BUFSIZE)
+ buffer.(line)
+
+ # control total time spent reading
+ runtime = Time.now - Thread.current[:cmd_start]
+ handle_timeout(runtime)
+ rescue Errno::EAGAIN, Errno::EINTR
+ rescue EOFError, Errno::EPIPE, Errno::EIO # thrown by PTY
+ readers.delete(reader)
+ reader.close
+ end
end
- rescue Errno::EIO
- # GNU/Linux `gets` raises when PTY slave is closed
- nil
- rescue => err
- raise err
- ensure
- stream.close
end
end
end
# @api private
def waitpid(pid)
- ::Process.waitpid(pid, Process::WUNTRACED)
- $?.exitstatus
+ _pid, status = ::Process.waitpid2(pid, ::Process::WUNTRACED)
+ status.exitstatus || status.termsig if _pid
rescue Errno::ECHILD
# In JRuby, waiting on a finished pid raises.
end
end # ProcessRunner
end # Command