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