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