66 lines
		
	
	
	
		
			2.3 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
			
		
		
	
	
			66 lines
		
	
	
	
		
			2.3 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
# frozen_string_literal: false
 | 
						|
 | 
						|
require 'fcntl'
 | 
						|
 | 
						|
module Terrapin
 | 
						|
  module MultiPipeExtensions
 | 
						|
    def initialize
 | 
						|
      @stdout_in, @stdout_out = IO.pipe
 | 
						|
      @stderr_in, @stderr_out = IO.pipe
 | 
						|
 | 
						|
      clear_nonblocking_flags!
 | 
						|
    end
 | 
						|
 | 
						|
    def pipe_options
 | 
						|
      # Add some flags to explicitly close the other end of the pipes
 | 
						|
      { :out => @stdout_out, :err => @stderr_out, @stdout_in => :close, @stderr_in => :close }
 | 
						|
    end
 | 
						|
 | 
						|
    def read
 | 
						|
      # While we are patching Terrapin, fix child process potentially getting stuck on writing
 | 
						|
      # to stderr.
 | 
						|
 | 
						|
      @stdout_output = +''
 | 
						|
      @stderr_output = +''
 | 
						|
 | 
						|
      fds_to_read = [@stdout_in, @stderr_in]
 | 
						|
      until fds_to_read.empty?
 | 
						|
        rs, = IO.select(fds_to_read)
 | 
						|
 | 
						|
        read_nonblocking!(@stdout_in, @stdout_output, fds_to_read) if rs.include?(@stdout_in)
 | 
						|
        read_nonblocking!(@stderr_in, @stderr_output, fds_to_read) if rs.include?(@stderr_in)
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    private
 | 
						|
 | 
						|
    # @param [IO] io IO Stream to read until there is nothing to read
 | 
						|
    # @param [String] result Mutable string to which read values will be appended to
 | 
						|
    # @param [Array<IO>] fds_to_read Mutable array from which `io` should be removed on EOF
 | 
						|
    def read_nonblocking!(io, result, fds_to_read)
 | 
						|
      while (partial_result = io.read_nonblock(8192))
 | 
						|
        result << partial_result
 | 
						|
      end
 | 
						|
    rescue IO::WaitReadable
 | 
						|
      # Do nothing
 | 
						|
    rescue EOFError
 | 
						|
      fds_to_read.delete(io)
 | 
						|
    end
 | 
						|
 | 
						|
    def clear_nonblocking_flags!
 | 
						|
      # Ruby 3.0 sets pipes to non-blocking mode, and resets the flags as
 | 
						|
      # needed when calling fork/exec-related syscalls, but posix-spawn does
 | 
						|
      # not currently do that, so we need to do it manually for the time being
 | 
						|
      # so that the child process do not error out when the buffers are full.
 | 
						|
      stdout_flags = @stdout_out.fcntl(Fcntl::F_GETFL)
 | 
						|
      @stdout_out.fcntl(Fcntl::F_SETFL, stdout_flags & ~Fcntl::O_NONBLOCK) if stdout_flags & Fcntl::O_NONBLOCK
 | 
						|
 | 
						|
      stderr_flags = @stderr_out.fcntl(Fcntl::F_GETFL)
 | 
						|
      @stderr_out.fcntl(Fcntl::F_SETFL, stderr_flags & ~Fcntl::O_NONBLOCK) if stderr_flags & Fcntl::O_NONBLOCK
 | 
						|
    rescue NameError, NotImplementedError, Errno::EINVAL
 | 
						|
      # Probably on windows, where pipes are blocking by default
 | 
						|
    end
 | 
						|
  end
 | 
						|
end
 | 
						|
 | 
						|
Terrapin::CommandLine::MultiPipe.prepend(Terrapin::MultiPipeExtensions)
 |