1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
|
# 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)
|