lib/r10k/util/subprocess/posix/runner.rb in r10k-1.3.5 vs lib/r10k/util/subprocess/posix/runner.rb in r10k-1.4.0
- old
+ new
@@ -37,12 +37,18 @@
if @pid
_, @status = Process.waitpid2(@pid)
end
stdout = @stdout_r.read
- stderr = @stderr_r.read
+ # Use non-blocking read for stderr_r to work around an issue with OpenSSH
+ # ControlPersist: https://bugzilla.mindrot.org/show_bug.cgi?id=1988
+ # Blocking should not occur in any other case since the process that was
+ # attached to the pipe has already terminated.
+ stderr = read_nonblock(@stderr_r)
+ @stdout_r.close
+ @stderr_r.close
@result = R10K::Util::Subprocess::Result.new(@argv, stdout, stderr, @status.exitstatus)
end
def run
start
@@ -86,10 +92,11 @@
if not exec_r.eof?
msg = exec_r.read || "exec() failed"
raise "Could not execute #{@argv.join(' ')}: #{msg}"
end
+ exec_r.close
end
# Create a pipe so that the parent can verify that the child process
# successfully executed. The pipe will be closed on a successful exec(),
# and will contain an error message on failure.
@@ -105,9 +112,29 @@
def attach_pipes
@stdout_r, @stdout_w = ::IO.pipe
@stderr_r, @stderr_w = ::IO.pipe
+ @stdout_r.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
+ @stdout_w.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
+ @stderr_r.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
+ @stderr_w.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
+
@io.stdout = @stdout_w
@io.stderr = @stderr_w
+ end
+
+ # Perform non-blocking reads on a pipe that could still be open
+ # Give up on reaching EOF or blocking and return what was read
+ def read_nonblock(rd_io)
+ data = ''
+ begin
+ # Loop until EOF or blocking
+ loop do
+ # do an 8k non-blocking read and append the result
+ data << rd_io.read_nonblock(8192)
+ end
+ rescue EOFError, Errno::EAGAIN, Errno::EWOULDBLOCK
+ end
+ data
end
end