- # 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)
|